(
KOMPENDIUM
)
Java 6
Kompendium Kompetent aufbereitetes PC-Know-how für alle Die KOMPENDIEN aus dem Markt+Technik Verlag stehen seit mehr als 20 Jahren für anerkanntes Expertenwissen und bieten wertvolle Praxistipps in allen Fragen rund um den PC. Das Portfolio der Handbücher reicht von der übersichtlichen Vorstellung diverser Programmiersprachen bis hin zur umfangreichen Beschreibung kompletter Betriebssysteme: Mit mehr als 500 Titeln seit Bestehen der Reihe wurde nahezu jede Fragestellung der Computerpraxis abgedeckt. Ob als Lehrbuch für den ambitionierten Einsteiger oder Nachschlagewerk für den erfahrenen Anwender: Die übersichtlichen, klar strukturierten KOMPENDIEN helfen jedem schnell weiter und auch komplexe Sachverhalte werden mit praxisnahen Beispielen übersichtlich illustriert und verständlich gemacht. Ein detailliertes Inhaltsverzeichnis und ein umfangreicher Index ermöglichen dem Leser außerdem schnellen Zugriff auf die gesuchten Informationen. Technisch anspruchsvoll und präzise, dabei jedoch immer praxisbezogen und klar verständlich: Das sind die KOMPENDIEN, die mit mehr als 6 Millionen Lesern zu den erfolgreichsten Computerfachbüchern auf dem deutschsprachigen Markt gehören.
Praxis der objektorientierten Programmierung DIRK LOUIS PETER MÜLLER
(
KOMPENDIUM Einführung | Arbeitsbuch | Nachschlagewerk
)
Bibliografische Information Der Deutschen Bibliothek Die Deutsche Bibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über abrufbar. 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. All rights reserved. No parts if this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopy, recording or by any information storage retrieval system, without permission from Pearson Education Inc. Fast alle Hardware- und Softwarebezeichnungen und weitere Stichworte und sonstige Angaben, die in diesem Buch verwendet werden, sind als eingetragene Marken geschützt. Da es nicht möglich ist, in allen Fällen zeitnah zu ermitteln, ob ein Markenschutz besteht, wird das Symbol ® in diesem Buch nicht verwendet. Umwelthinweis: Dieses Buch wurde auf chlorfrei gebleichtem Papier gedruckt.
10 9 8 7 6 5 4 3 2 1 09 08 07
ISBN 978-3-8272-4107-8
© 2007 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 Coverkonzept: independent Medien-Design, Widenmayerstraße 16, 80538 München Covergestaltung: Thomas Arlt,
[email protected] Titelfoto: Downtown Los Angeles Skyline, CA Fotograf: Joe Sohm Bildagentur: Jupiter Images, München-Ottobrunn Lektorat: Brigitte Bauer-Schiewek,
[email protected] Korrektorat: Petra Alm Herstellung: Elisabeth Prümm,
[email protected] Satz: reemers publishing services gmbh, Krefeld Druck und Verarbeitung: Kösel, Krugzell (www.KoeselBuch.de) Printed in Germany
Überblick
Überblick
Vorwort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
29
Teil 1
Einführung
33
Kapitel 1
Java war früher eine Eiche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
35
Kapitel 2
Einführung für Programmieranfänger . . . . . . . . . . . . . . . . . . . . . . . . . .
43
Kapitel 3
Das erste Programm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
79
Kapitel 4
Java-Stilkonventionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
105
Teil 2
Java-Grundlagen
109
Kapitel 5
Daten und Datentypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
111
Kapitel 6
Operatoren und Ausdrücke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
149
Kapitel 7
Kontrollstrukturen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
181
Kapitel 8
Klassen und Objekte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
217
Kapitel 9
Arrays und Aufzählungen (enum) . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
271
Kapitel 10 Pakete, Gültigkeitsbereiche und andere Fragen . . . . . . . . . . . . . . . . . .
299
Kapitel 11
Stöbern und Entspannen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
323
Teil 3
Objektorientierte Programmierung in Java
337
Kapitel 12 Objektorientiert denken – objektorientiert programmieren . . . . . . . . .
339
Kapitel 13 Vererbung und Komposition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
355
Kapitel 14 Polymorphie und generische Programmierung . . . . . . . . . . . . . . . . . . .
381
Kapitel 15 Abstrakte Klassen und Schnittstellen (Interfaces) . . . . . . . . . . . . . . . . .
415
Kapitel 16 Fehlerbehandlung mit Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . .
431
Kapitel 17
451
Programmieren mit Objekten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5
Überblick
Teil 4
GUI-Programmierung
Kapitel 18 Grafische Benutzeroberflächen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
469 471
Kapitel 19 Benutzeroberflächen mit Swing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
507
Kapitel 20 Steuerelemente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
525
Kapitel 21 Menüs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
545
Kapitel 22 Dialogfenster . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
559
Kapitel 23 Grafik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
571
Kapitel 24 Textverarbeitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
617
Kapitel 25 Drucken. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
647
Kapitel 26 GUI-Ergänzungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
673
Kapitel 27 Programm: Das Java-Millionenquiz . . . . . . . . . . . . . . . . . . . . . . . . . . .
711
Teil 5
725
Weiterführende und ergänzende Techniken
Kapitel 28 Strings und Utilities. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
727
Kapitel 29 Container (Collections) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
757
Kapitel 30 Thread-Programmierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
791
Kapitel 31 Ein- und Ausgabe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
829
Kapitel 32 Ressourcen und Lokalisierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
895
Teil 6
919
Spezielle Programmiergebiete
Kapitel 33 Datenbankzugriffe mit JDBC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
921
Kapitel 34 JDBC – Vertiefung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
947
Kapitel 35 Netzwerkprogrammierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
967
Kapitel 36 HTTP-Verbindungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1001 Kapitel 37 Verteilte Anwendungen mit RMI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1017 Kapitel 38 Applets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1041 Kapitel 39 JNI, eine Schnittstelle zu C/C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1063 Kapitel 40 Sicherheit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1083 Kapitel 41 Annotationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1097 Kapitel 42 XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1103 Kapitel 43 Webservices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1129
6
Anhänge
1137
Anhang A Die Java-Tools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1139 Anhang B Installation von MySQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1165 Anhang C
Zahlensysteme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1169
Anhang D Tabellen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1173 Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1181
7
Überblick
Teil 7
Inhalt
Inhalt
Vorwort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
29
Teil 1
Einführung
33
Kapitel 1
Java war früher eine Eiche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
35
Kapitel 2
Einführung für Programmieranfänger . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
43
2.1
Vom Maschinencode zu den höheren Programmiersprachen . . . . . . . . . . . . . . Assembler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Das Variablenkonzept . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die höheren Programmiersprachen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Kontrollstrukturen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
44 44
Die strukturierte Programmierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Typisierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
52 52
Die objektorientierte Programmierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . Selbst definierte Datentypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Objektorientierte Problemlösung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Objektorientierte Programmierung in Java . . . . . . . . . . . . . . . . . . . . . . . . . . . .
62 62
2.2
2.3
45 46 49
58
67 69
2.4
Noch Fragen? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
77
Kapitel 3
Das erste Programm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
79
3.1
Programmerstellung in Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Portierbarkeit und Robustheit. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Effizienz und Schutz geistigen Eigentums . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Das Java-Modell und die Virtual Machine . . . . . . . . . . . . . . . . . . . . . . . . . . . .
79 79
Installation des JDK . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Installation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Anpassen des Systems. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Wo Sie weitere Hilfe finden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
83 84
3.2
82 82
86 89
9
Inhalt
3.3
Welche Art Programm darf es sein? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
89
3.4
Konsolenanwendungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ein einfaches Konsolen-Grundgerüst . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Konsolenanwendungen erstellen und ausführen . . . . . . . . . . . . . . . . . . . . . . .
89 90
Ein- und Ausgabe für Konsolenanwendungen . . . . . . . . . . . . . . . . . . . . . . . Ausgabe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Eingabe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
94 94
3.6
Klassen, Pakete und die Standardbibliothek . . . . . . . . . . . . . . . . . . . . . . . .
101
Kapitel 4
Java-Stilkonventionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
105
Teil 2
Java-Grundlagen
109
Kapitel 5
Daten und Datentypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
111
5.1
Das Java-Datenmodell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Repräsentation von Daten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Das Typenkonzept . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . An Datentypen gebundene Konzepte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
111 112
Variablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Deklaration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Regeln für die Namensgebung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Initialisierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Gültigkeitsbereiche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
118 119
5.3
Literale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
123
5.4
Symbolische Konstanten – konstante Variablen . . . . . . . . . . . . . . . . . . . . . .
124
5.5
Die elementaren Datentypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Integer-Typen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Gleitkommatypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Zeichen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . boolean . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
125 125
Die Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . String-Literale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die String-Operatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . String-Typumwandlungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
137 137
Typumwandlung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Automatische oder implizite (»widening«) Typumwandlungen . . . . . . . . . . . . . . Explizite (»narrowing«) Typumwandlungen . . . . . . . . . . . . . . . . . . . . . . . . . .
142 143
3.5
5.2
5.6
5.7
10
91
97
113 118
120 121 122
128 133 136
138 140
144
Inhalt 5.8
Wrapper-Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Nützliche Klassenelemente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Autoboxing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
145 146
Kapitel 6
Operatoren und Ausdrücke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
149
6.1
Allgemeines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Unär, binär, ternär . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Binäre numerische Promotionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ausdrücke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Anweisungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
150 151
Operationen auf allen Datentypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Der Zuweisungsoperator = . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Gleichheitsoperatoren == und != . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Der Typumwandlungsoperator (typ) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
153 153
Operationen auf numerischen Datentypen . . . . . . . . . . . . . . . . . . . . . . . . . . Vorzeichen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die arithmetischen Operatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Inkrement und Dekrement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Bit-Operatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die kombinierten Zuweisungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Vergleichsoperatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Klasse Math . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Zufallszahlen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
155 155
6.4
Operationen auf boolean . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die logischen Operatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
168 168
6.5
Operationen auf Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Der Konkatenationsoperator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Klasse String . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
168 168
6.6
Operationen auf Referenztypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Basisklasse Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
171 171
6.7
Sonstige Operatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
174
6.8
Reihenfolge der Ausdrucksauswertung . . . . . . . . . . . . . . . . . . . . . . . . . . . . Priorität . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Klammerung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Operandenauswertung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Prioritätentabelle der Operatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
175 175
Nebeneffekte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
179
6.2
6.3
6.9
147
151 152 152
154 155
155 157 158 161 162 162 165
169
175 176 177
11
Inhalt
Kapitel 7
Kontrollstrukturen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
181
7.1
Entscheidungen und Bedingungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Bedingungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Vergleichsoperatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . String-Vergleiche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die logischen Operatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
181 183
Verzweigungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die einfache if-Anweisung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die if-else-Verzweigung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Der Bedingungsoperator ?: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die switch-Verzweigung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
190 190
Schleifen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die while-Schleife . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die do-while-Schleife . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die for-Schleife . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Endlosschleifen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Schleifen mit mehreren Schleifenvariablen . . . . . . . . . . . . . . . . . . . . . . . . . . . Performance-Tipps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
198 198
Sprunganweisungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbruchbefehle für Verzweigungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbruchbefehle für Schleife . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbruchbefehle mit Sprungmarken . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
206 208
Fallstricke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die leere Anweisung ; . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Nebeneffekte in booleschen Ausdrücken . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Warteschleifen (?) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
212 212
Kapitel 8
Klassen und Objekte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
217
8.1
Die Klassendefinition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Syntax . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Instanzbildung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Mit Objekten programmieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
217 217
Felder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Initialisierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Objektvariablen als Felder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Konstante Felder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Statische Felder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Instanzvariablen und Klassenvariablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
224 225
7.2
7.3
7.4
7.5
8.2
12
184 185 188
191 194 194
202 203 204 205 205
208 211
214 214
222 223
226 226 228 229
Inhalt 8.3
Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Methodendefinition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Datenverarbeitung und Datenaustausch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Parameter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Rückgabewert . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . this . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Statische Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Überladung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Methoden mit variabler Anzahl Argumente . . . . . . . . . . . . . . . . . . . . . . . . . . .
229 230
Der Konstruktor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Klassen ohne selbst definierten Konstruktor . . . . . . . . . . . . . . . . . . . . . . . . . . . Der Standardkonstruktor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
251 252
Zugriffsspezifizierer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Zugriffsspezifizierer für Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Zugriffsspezifizierer für Klassenelemente . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
254 256
Klassen-Design . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Was ist eine »gute« Klasse? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Auswahl der Klassenelemente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Zugriffsrechte und öffentliche Schnittstelle . . . . . . . . . . . . . . . . . . . . . . . . . . . Einsatz des Konstruktors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Objektauflösung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Einsatz von Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Klassen als Methodensammlungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Klassenerzeugung und statischer Klassencode . . . . . . . . . . . . . . . . . . . . . . . . .
258 258
Kapitel 9
Arrays und Aufzählungen (enum) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
271
9.1
Deklaration und Erzeugung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Variablendeklaration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Array-Erzeugung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
271 272
Auf Array-Elemente zugreifen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Der indizierte Zugriff. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Arrays in Schleifen durchlaufen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
274 275
Programmieren mit Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Array-Länge bestimmen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . java.util.Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Arrays als Parameter. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Befehlszeilenargumente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
277 277
8.4
8.5
8.6
9.2
9.3
232 235 240 242 245 247 249
252
256
259 259 264 264 267 268 269
272
275
277 282 283
13
Inhalt
9.4
Mehrdimensionale Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Deklaration und Objekterzeugung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Speicherbelegung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Vorteile und Anwendung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Dynamisch erzeugte Unterarrays variabler Länge . . . . . . . . . . . . . . . . . . . . . . . .
285 285
Aufzählungen (enum) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Definition und Verwendung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Eigene enum-Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
291 292
Kapitel 10
Pakete, Gültigkeitsbereiche und andere Fragen . . . . . . . . . . . . . . . . . . . . . .
299
10.1
Pakete . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Klassen aus anderen Paketen verwenden . . . . . . . . . . . . . . . . . . . . . . . . . . . . Statische Elemente aus anderen Paketen verwenden . . . . . . . . . . . . . . . . . . . . . Klassen einem Paket zuordnen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Pakete auf der Festplatte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Pakete kompilieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
299 301
Gültigkeitsbereiche und Lebensdauer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Gültigkeitsbereiche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Redeklaration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Verdeckung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Lebensdauer von Variablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Lebensdauer von Objekten. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Übersicht Variablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
305 305
10.3
Blöcke, lokale Variablen und »definitive Assignment« . . . . . . . . . . . . . . . . . .
315
10.4
Innere Klassen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
319
Kapitel 11
Stöbern und Entspannen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
323
11.1
Rekursion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
323
11.2
Zahlen beliebiger Genauigkeit mit BigDecimal und BigInteger . . . . . . . . . . . .
325
11.3
Über- und Unterlauf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
328
11.4
cos(90 Grad) ist nicht gleich 0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
330
11.5
Speicherbereiche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
332
11.6
Klammerung und Kellerautomat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
334
9.5
10.2
14
286 287 289
296
301 303 303 304
307 308 310 311 314
Inhalt Teil 3
Objektorientierte Programmierung in Java
337
Kapitel 12
Objektorientiert denken – objektorientiert programmieren . . . . . . . . . . . . . .
339
12.1
Objektorientiertes Programmieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Denken Sie in Objekten! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Wie sind Objekte beschaffen? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
339 339
Wie findet man einen objektorientierten Lösungsansatz? . . . . . . . . . . . . . . . Moderne Software-Entwicklung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Fallbeispiel – Temperaturregelung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Programme aus mehreren Quelldateien . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
341 342 351
Kapitel 13
Vererbung und Komposition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
355
13.1
Das Prinzip der Vererbung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Der grundlegende Mechanismus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Der Sinn der Vererbung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Einige wichtige Fakten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
355 355
Wie greift man auf geerbte Elemente zu? . . . . . . . . . . . . . . . . . . . . . . . . . . Geerbte Elemente bilden Unterobjekte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Vier Zugriffsebenen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
358 359
12.2
13.2
13.3
340
344
357 358
361
Wie initialisiert man geerbte Elemente? . . . . . . . . . . . . . . . . . . . . . . . . . . . Konstruktor und Basisklassenkonstruktor . . . . . . . . . . . . . . . . . . . . . . . . . . . . Expliziter Aufruf eines Basisklassenkonstruktors . . . . . . . . . . . . . . . . . . . . . . . .
366 366
Verdecken, überschreiben und überladen . . . . . . . . . . . . . . . . . . . . . . . . . . Verdeckung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Überschreibung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Überladung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
372 372
Vererbung und objektorientiertes Design . . . . . . . . . . . . . . . . . . . . . . . . . . Wann ist Vererbung gerechtfertigt? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Vererbung versus Komposition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Klassenhierarchien . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
377 377 379
Kapitel 14
Polymorphie und generische Programmierung . . . . . . . . . . . . . . . . . . . . . . .
381
14.1
Polymorphe Variablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abgeleitete Objekte als Basisklassenobjekte verwenden . . . . . . . . . . . . . . . . . . . Rückverwandlung in Objekte abgeleiteter Klassen . . . . . . . . . . . . . . . . . . . . . . .
381 383
Polymorphe Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Polymorphie durch Überschreibung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . »Echte« Polymorphie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
384 385
13.4
13.5
14.2
368
374 376
379
383
386
15
Inhalt
Dynamische und statische Bindung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Polymorphes Verhalten unterdrücken? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
387
Generische Programmierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Basisklassen-Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Basisklassenparameter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Typidentifizierung zur Laufzeit (RTTI) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Noch einmal: Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
391 391
Java Generics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Syntax . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Java Generics kontra Object-Implementierung . . . . . . . . . . . . . . . . . . . . . . . . . Wildcard-Typ und Einschränkungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Vererbung und Überladung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Hinter den Kulissen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
399 401
Kapitel 15
Abstrakte Klassen und Schnittstellen (Interfaces) . . . . . . . . . . . . . . . . . . . . .
415
15.1
Abstrakte Klassen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abstrakte Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abstrakte Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
415 418
Schnittstellen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Was sind Schnittstellen? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Schnittstellen definieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Schnittstellen implementieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Schnittstellen und Polymorphie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Mehrere Schnittstellen implementieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Schnittstellen durch anonyme Klassen implementieren. . . . . . . . . . . . . . . . . . . .
419 421
Kapitel 16
Fehlerbehandlung mit Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
431
16.1
Möglichkeiten der Fehlerbehandlung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Fehler an Ort und Stelle abfangen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Fehler durch Rückgabewerte codieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Fehlerbehandlung durch Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
431 431
Exceptions abfangen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Mehrere catch-Handler. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Exception-Hierarchie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Der Parameter des catch-Handlers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
438 439
Exceptions weiterleiten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Weiterleitung mit throws. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exceptions und Behandlungszwang . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
443 443
14.3
14.4
15.2
16.2
16.3
16
390
393 396 399
403 405 409 412
418
423 424 424 426 430
434 435
440 442
444
Inhalt 16.4
Exceptions auslösen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exceptions erneut auslösen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exceptions verketten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
444 444
16.5
Eigene Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
446
16.6
Programmfluss und Exception-Behandlung . . . . . . . . . . . . . . . . . . . . . . . . . Die Problematik des gestörten Programmflusses . . . . . . . . . . . . . . . . . . . . . . . . finally . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
447 447 448
Kapitel 17
Programmieren mit Objekten. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
451
17.1
Objekte auf Konsole ausgeben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
451
17.2
Objekte kopieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Flaches Kopieren mit clone() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Tiefes Kopieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
453 454
Objekte vergleichen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Gleichheit feststellen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Größenvergleiche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
460 461
Teil 4
GUI-Programmierung
469
Kapitel 18
Grafische Benutzeroberflächen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
471
18.1
Das Abstract Window Toolkit (AWT) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
472
18.2
Das Grundgerüst einer grafischen Benutzeroberfläche . . . . . . . . . . . . . . . . . . Programm erstellen und ausführen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Programm erweitern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
473 475
17.3
18.3
18.4
445
458
465
475
Komponenten einfügen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Komponentenhierarchie (AWT) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Komponenten erzeugen und konfigurieren . . . . . . . . . . . . . . . . . . . . . . . . . . . Beschriftungsfeld – Label . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Schaltfläche – Button . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Kontrollkästchen – Checkbox . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Optionsfelder – CheckboxGroup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Grundflächen – Panel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Heavyweight- und Lightweight-Komponenten . . . . . . . . . . . . . . . . . . . . . . . .
476 476
Komponenten anordnen: Layout-Manager . . . . . . . . . . . . . . . . . . . . . . . . . Die Layout-Manager des AWT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Der FlowLayout-Manager . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Der GridLayout-Manager . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
486 487
478 481 483 483 484 485 486
487 488
17
Inhalt
Der BorderLayout-Manager . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Der CardLayout-Manager . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Der GridBagLayout-Manager . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Positionieren ohne Layout-Manager . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Komponenten verschachteln . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18.5
490 491 492 493 493
Ereignisse behandeln . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Grundlegender Mechanismus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ereignisse und Ereignisempfänger (Listener) . . . . . . . . . . . . . . . . . . . . . . . . . . . Adapterklassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Ereignisparameter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
494 495
18.6
Beispielprogramm mit AWT-Komponenten . . . . . . . . . . . . . . . . . . . . . . . . .
503
Kapitel 19
Benutzeroberflächen mit Swing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
507
19.1
AWT und Swing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
507
19.2
Modell-Ansicht-Steuerung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
508
19.3
Ein Grundgerüst für Benutzeroberflächen mit Swing . . . . . . . . . . . . . . . . . . .
508
19.4
Swing-Komponenten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Komponenten einfügen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Größenfestlegung für Komponenten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
511 511
Layout-Manager von Swing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Der BoxLayout-Manager und die Containerklasse Box . . . . . . . . . . . . . . . . . . . . Der GroupLayout-Manager und die Klasse Group . . . . . . . . . . . . . . . . . . . . . . .
513 513
19.6
Austauschbares Erscheinungsbild (Look&Feel) . . . . . . . . . . . . . . . . . . . . . . .
519
19.7
Swing-Beispielprogramm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
520
Kapitel 20
Steuerelemente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
525
20.1
Swing-Komponenten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
525
20.2
JComponent . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
527
20.3
Beschriftungsfelder – JLabel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
529
20.4
Schaltflächen, Kontrollkästchen und Optionsfelder . . . . . . . . . . . . . . . . . . . . Schaltflächen – JButton . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Wechselschalter – JToggleButton . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Schaltflächengruppen – ButtonGroup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Kontrollkästchen – JCheckBox . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Optionsfelder – JRadioButton . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
530 531
19.5
18
499 501 502
512
515
532 534 536 539
Inhalt 20.5
Listen/Kombinationsfelder – JList und JComboBox . . . . . . . . . . . . . . . . . . . .
540
20.6
Fortschrittsanzeige – JProgressBar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
543
Kapitel 21
Menüs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
545
21.1
Erstellen von Menüs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Aufbau einer Menüleiste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Kontextmenüs (Popup-Menüs) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ereignisbehandlung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
546 546
Tastaturkürzel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Mnemonics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Accelerators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
552 552
21.3
Symbolleisten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
554
21.4
Die Zwischenablage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
555
Kapitel 22
Dialogfenster . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
559
22.1
Standarddialoge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . JOptionPane . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . JFileChooser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . JColorChooser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
559 559
22.2
Eigene Dialoge mit JDialog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
565
22.3
Modale und nicht modale Dialoge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
569
Kapitel 23
Grafik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
571
23.1
Grafik-Grundlagen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Gerätekontexte und Graphics-Objekte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . paint() und paintComponent() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Zeichengeräte und -methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Zeichnen in AWT und Swing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
571 572
Praxis der Grafikprogrammierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Automatisches Neuzeichnen mit paint() / paintComponent(). . . . . . . . . . . . . . . . . Außerhalb von paintComponent() zeichnen . . . . . . . . . . . . . . . . . . . . . . . . . . . Swing und das Double Buffering . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Computergrafiken . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
585 585
21.2
23.2
23.3
Java2D . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Zeichnen in Java2D . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Festlegen des Strichstils . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Füllmuster . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
549 550
553
561 564
573 574 581
588 590 591 595 596 597 598
19
Inhalt
Methoden und Klassen zum Zeichnen in Java2D . . . . . . . . . . . . . . . . . . . . . . . . Skalieren und Drehen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23.4
599 602
Bilder und Bilddateien . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Bilder laden und speichern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Bilder anzeigen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Bilder bearbeiten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
604 604
Clipping . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Geschützte Bereiche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Effektives Neuzeichnen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Zeichnen mit Clipping-Effekten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
611 612
Kapitel 24
Textverarbeitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
617
24.1
Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Dokument und Ansicht (MVC-Architektur) . . . . . . . . . . . . . . . . . . . . . . . . . . . . Positionsanzeige (Caret) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Textmarkierungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Vordefinierte Ereignisbehandlung (Actions) . . . . . . . . . . . . . . . . . . . . . . . . . . . Scrollbarkeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
617 618
24.2
Einzeilige Textfelder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
624
24.3
Texteditierung mit JTextArea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Einrichtung einer scrollbaren JTextArea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Laden und Speichern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Schriftart und -stil wählen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Zwischenablage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Suchen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
627 629
Kapitel 25
Drucken . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
647
25.1
Das Grundmodell und die APIs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Das Grundprinzip – Drucken unter Java 1.2 . . . . . . . . . . . . . . . . . . . . . . . . . . . Stilles Drucken ohne Benutzer – die javax.print-Erweiterung . . . . . . . . . . . . . . . . Texte drucken einfach gemacht – die print()-Methoden von Java 1.6 . . . . . . . . . . .
647 647
Die Klassen und Schnittstellen zum Drucken. . . . . . . . . . . . . . . . . . . . . . . . . Die Schnittstelle Printable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Klasse PrinterJob . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Klasse PrintServiceLookup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Attribute für die Druckparameter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die print()-Methoden der Textkomponenten . . . . . . . . . . . . . . . . . . . . . . . . . .
650 650
Bilder drucken . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
661
23.5
25.2
25.3
20
605 606
612 613
621 621 622 623
630 637 642 643
648 649
652 655 658 660
Inhalt Variante 1: Drucken mit eigener Printable-Implementierung . . . . . . . . . . . . . . . . Variante 2: Stilles Drucken aus Bilddatei . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25.4
661 666
Texte drucken . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Variante 1: Drucken mit print() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Variante 2: Drucken mit getPrintable(). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
667 667
Kapitel 26
GUI-Ergänzungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
673
26.1
Spezielle Container . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . JTabbedPane und JSplitPane . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . JDesktopPane und JInternalFrame . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
673 673
Baum- und Tabellendarstellung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . JTree . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . JTable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
677 677
Anzeigen eines Startbildschirms (Splash-Screen) . . . . . . . . . . . . . . . . . . . . . Startbildschirme anzeigen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Startbildschirme und JAR-Archive . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Programmierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
690 691
26.4
Der Infobereich der Taskleiste (SystemTray) . . . . . . . . . . . . . . . . . . . . . . . . .
693
26.5
Drag&Drop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Drag-Unterstützung aktivieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Drop-Unterstützung konfigurieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Drag&Drop für Labels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Datei-Drop für JTextArea-Komponenten (eigener TransferHandler) . . . . . . . . . . . .
697 697
Sound . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Sound mit der Klasse Applet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Java Sound API. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
704 704
Kapitel 27
Programm: Das Java-Millionenquiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
711
Teil 5
Weiterführende und ergänzende Techniken
725
Kapitel 28
Strings und Utilities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
727
28.1
Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Zeichensätze und Codierungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Klassen StringBuilder und StringBuffer . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Klasse StringTokenizer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
727 727
26.2
26.3
26.6
670
676
682
691 691
698 698 699 700
706
733 736
21
Inhalt
28.2
28.3
Stringvergleiche mit regulären Ausdrücken . . . . . . . . . . . . . . . . . . . . . . . . . . .
737
Zeit und Datum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Klasse Date . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Klasse Calendar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Klasse DateFormat. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
741 741
Systemprogrammierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Runtime und Process . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . System . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Zeitgeber . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
748 748
742 744 745
750 752 753
Kapitel 29
Container (Collections) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
757
29.1
Programmieren mit Containern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Container auswählen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Container erzeugen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Elementare Operationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
759 759
29.2
Listen (List) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
763
29.3
Mengen (Set) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
770
29.4
Warteschlangen (Queue) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
773
29.5
Wörterbücher (Map) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
776
29.6
Iteratoren und for-Schleife . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Iteratoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die for-Schleife für Collections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
780 781
Suchen und Sortieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Schnittstelle Comparable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Schnittstelle Comparator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Klasse Collections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
783 784
Kapitel 30
Thread-Programmierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
791
30.1
Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
791
30.2
Threads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Klasse Thread . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Schnittstelle Runnable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Schlafen und Unterbrechen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Dämonen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Nützliche Thread-Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
793 793
29.7
22
760 761
783
785 787
799 800 802 803
Inhalt 30.3
Synchronisierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . volatile-Variablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Gesicherte Abschnitte: synchronized . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Nachrichten mit wait() und notify() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Pipes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
804 806
Verwaltung von Threads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Thread-Scheduling und Prioritäten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Thread-Gruppen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
815 815
Threads und Swing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Single-Thread-Rule . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Hintergrundaktivität mit SwingWorker . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Periodische Aktivitäten mit Timer. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
821 821
30.6
Threads und Collections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
828
Kapitel 31
Ein- und Ausgabe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
829
31.1
Dateien und Verzeichnisse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Dateinamen und Dateipfade . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Dateien anlegen und löschen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Verzeichnisse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
829 829
Ein- und Ausgabestreams . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Das Stream-Konzept . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . InputStream/OutputStream und Reader/Writer . . . . . . . . . . . . . . . . . . . . . . . . . Ein-/Ausgabe in Dateien . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Daten zurückschreiben mit PushbackInputStream und PushbackReader . . . . . . . . . Ein-/Ausgabe in den Speicher . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Standardeingabe und -ausgabe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . StreamTokenizer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Serialisierung von Objekten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
836 837
Formatieren und Scannen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Scanner . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Formatter und die Formatierungsstrings . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
863 863
Verbesserte Konsolenunterstützung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Arbeiten mit Console . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Problematik der Umlaute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Passworteingaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . readLine() mit gleichzeitiger Ausgabe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
874 874
New Java I/O (java.nio) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Kanäle und Puffer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
877 877
30.4
30.5
31.2
31.3
31.4
31.5
806 809 813
818
822 825
831 832
839 840 849 850 853 855 857
868
875 876 877
23
Inhalt
Datenkonvertierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Direkter Datentransfer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Memory Mapping . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Dateisperren (File Locking) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
883
Kapitel 32
Ressourcen und Lokalisierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
895
32.1
Ressourcen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Vorteile externer Ressourcen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Strings laden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Bilder laden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ressourcendateien im XML-Format . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
895 895
Internationalisierung und Lokalisierung . . . . . . . . . . . . . . . . . . . . . . . . . . . Lokale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ressourcendateien (und -bündel) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Nationale Formatierungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Stringvergleiche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
906 907
Teil 6
Spezielle Programmiergebiete
919
Kapitel 33
Datenbankzugriffe mit JDBC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
921
33.1
Datenbank-Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die relationale Datenbank . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . JDBC und ODBC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . SQL – Structured Query Language . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
921 921
33.2
MySQL-Datenbank anlegen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
928
33.3
Datenbankverbindung aufbauen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Das Laden des Treibers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Einsatz von DriverManager und Connection . . . . . . . . . . . . . . . . . . . . . . . . . . .
932 932
SQL-Abfragen durchführen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Klasse Statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Klasse ResultSet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
935 935
33.5
Java-DB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
943
Kapitel 34
JDBC – Vertiefung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
947
34.1
Metadaten ermitteln . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . DatabaseMetaData . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ResultSetMetaData . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
947 947
32.2
33.4
24
886 888 891
896 901 902
912 914 917
923 923
933
937
949
Inhalt 34.2
Transaktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
949
34.3
Vorbereitete Abfragen und Batchläufe . . . . . . . . . . . . . . . . . . . . . . . . . . . . PreparedStatement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Batch Update . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
951 951
SQL-/Java-Datentypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Typenzuordnung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Der JDBC-Typ BLOB/CLOB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
955 955
JDBC-Fehlerbehandlung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . SQLException . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . SQLWarning . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
960 960
34.6
Datenbank-Zugriffe über Applets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
962
Kapitel 35
Netzwerkprogrammierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
967
35.1
Netzwerke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Der TCP/IP-Stack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . IP-Adressen, Ports und Domain-Namen . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
967 969
Socketverbindungen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Adressierung mit InetAddress . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Stream-Sockets (TCP-Sockets) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Datagram-Sockets (UDP-Sockets) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Multicast-Sockets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
974 974
Non-Blocking I/O . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Stream-Sockets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Datagram-Sockets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
993 994
34.4
34.5
35.2
35.3
953
957
961
970
976 984 989
998
Kapitel 36
HTTP-Verbindungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1001
36.1
Uniform Resource Locator (URL) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1001
36.2
URL mit Sonderzeichen: x-www-form-urlencoded . . . . . . . . . . . . . . . . . . . . 1004
36.3
HTTP-Sessions durchführen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1004
36.4
Das HTTP-Protokoll. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die HTTP-Anfrage (Request) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die HTTP-Antwort (Response) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . HTTP - Header . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ablauf einer HTTP-Kommunikation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
36.5
1007 1008 1009 1010 1011
HTTP-Zugriffe über Proxy und Passwort . . . . . . . . . . . . . . . . . . . . . . . . . . . 1013
25
Inhalt
Kapitel 37
Verteilte Anwendungen mit RMI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1017
37.1
Grundarchitektur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1017
37.2
Objekte finden: die RMI-Systemregistrierung1019
37.3
Erstellen einer lokalen RMI-Anwendung . . . . . . . . . . . . . . . . . . . . . . . . . . . 1020
37.4
Parameterübergabe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1026
37.5
Erstellen einer verteilten RMI-Anwendung . . . . . . . . . . . . . . . . . . . . . . . . . 1031 Bereitstellen eines Security-Managers. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1032 Definition der Codebase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1033
37.6
Ergänzungen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1039 Automatischer Start der Systemregistrierung . . . . . . . . . . . . . . . . . . . . . . . . . . . 1039 Remote-Implementierung ohne UnicastRemoteObject . . . . . . . . . . . . . . . . . . . . 1040
Kapitel 38
Applets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1041
38.1
Das Applet-Grundgerüst . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Basisklasse: Applet versus JApplet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Der Applet-Lebenszyklus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . AWT-Applets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Kompilierung und Ausführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Anzeige im Browser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1041 1041 1043 1043 1045 1046
38.2
Swing-Applets. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1050
38.3
Applets und Threads. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1051
38.4
Applets und Multimedia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1053 Bilddateien laden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1053 Sounddateien laden. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1053
38.5
Parameterübergabe an Applets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1054
38.6
Applets und JAR-Archive . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1060
38.7
Applets und die Sicherheit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1061
Kapitel 39
JNI, eine Schnittstelle zu C/C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1063
39.1
Aufruf von nativem Code – Einstieg . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1064
39.2
Übergabeparameter und Rückgabewerte . . . . . . . . . . . . . . . . . . . . . . . . . . . 1067 Primitive Datentypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1068 Referenzen auf Objekte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1069
39.3
Zugriff auf Java-Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1077
26
Inhalt Kapitel 40
Sicherheit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1083
40.1
Das Laden von Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1083
40.2
Der Sicherheitsmanager (Security-Manager) . . . . . . . . . . . . . . . . . . . . . . . . . 1086
40.3
Signierter Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1090 Schritte des Software-Entwicklers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1092 Schritte des Kunden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1094
Kapitel 41
Annotationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1097
41.1
Vordefinierte Annotationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1097
41.2
Selbst definierte Annotationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1099
Kapitel 42
XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1103
42.1
Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1103
42.2
Aufbau von XML-Dokumenten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Elemente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die XML-Deklaration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Textinhalt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Processing Instructions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Kommentare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Wohlgeformtheit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1104 1104
DTD und XML Schema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . DTD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . XML Schemata . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Namensräume (Namespaces) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1108 1108
XML-Dokumente parsen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . SAX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . DOM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . JDOM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1111 1112
42.3
42.4
1106 1106 1107 1107 1107
1110 1111
1117 1125
Kapitel 43
Webservices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1129
43.1
Was ist ein Webservice? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1129
43.2
Webservices definieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1130
43.3
Webservices veröffentlichen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1133
43.4
Webservices aufrufen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1134
27
Inhalt
Teil 7
Anhänge
Anhang A
Die Java-Tools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1139
44.1
javac – der Compiler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Arbeitsweise. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Optionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Aufrufe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1137
1139 1140 1144 1146
44.2
java – der Interpreter. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1155
44.3
jar – Archive erstellen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1157
44.4
javadoc – Dokumentationen erstellen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1160
44.5
jdb – der Debugger . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1161
44.6
Weitere Tools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1163
Anhang B
Installation von MySQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1165
45.1
Der MySQL-Server . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1165
45.2
Der JDBC-Treiber . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1167
Anhang C
Zahlensysteme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1169
46.1
Umrechnungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1169
46.2
Bits und Bytes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1170
46.3
Die Hexadezimalzahlen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1170
Anhang D
Tabellen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1173
47.1
Java-Schlüsselwörter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1173
47.2
Java-Datentypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1174
47.3
Java-Operatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1174
47.4
Unicode-Zeichen (ASCII 0 bis 127) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1176
47.5
Unicode-Zeichen (Umlaute und Sonderzeichen)1177
47.6
Unicode-Zeichen (griechisches Alphabet) . . . . . . . . . . . . . . . . . . . . . . . . . . . 1178
47.7
HTTP-Statusnummern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1179 Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1181
28
Vorwort
Die Programmiersprache Java liegt mittlerweile in der sechsten Version vor und hat nach einigen stürmischen Anfangsjahren nun einen hohen Grad an Stabilität, Performanz und Funktionalität erreicht. Es ist daher auch nicht verwunderlich, dass Java neben C++ heutzutage die wichtigste Programmiersprache für Neuentwicklungen von kommerziellen Software-Produkten ist und sich auch im Hobby- und Freizeitbereich großer Beliebtheit erfreut. Gerade der Trend zu Anwendungen, die sich multimedial präsentieren müssen, d. h. Internet- und Netzwerkfähigkeit, mit Grafik- und Soundverarbeitung und einer ansprechenden grafischen Benutzeroberfläche, lässt viele Entwickler zu Java greifen. Leider hat das Aufblühen von Java einen kleinen Schönheitsfehler: Die Komplexität ist mit den Möglichkeiten gewachsen. Während man immer noch relativ schnell die Grundlagen der Sprache erlernen kann, ist das Erforschen und effiziente Nutzen der Java-Klassenbibliothek, die von mehreren hundert Klassen in der Version 1.0 auf aktuell über 6.500 explodiert ist, zu einer Herkulesaufgabe geworden. Dies stellt den Java-Neuling – aber auch den erfahrenen Java-Kenner – vor das Problem, den Überblick zu bewahren und zu wissen, welche Möglichkeiten in der aktuellen Java-Klassenbibliothek bereits schlummern, um zeitraubende und fehleranfällige Eigenentwicklungen zu vermeiden.
29
Index
Vor diesem Hintergrund versteht sich dieses Buch daher als ein Lehr- und Nachschlagebuch für Einsteiger und fortgeschrittene Leser, das neben einer Einführung in die grundlegenden Prinzipien der objektorientierten Programmierung alle wichtigen Aspekte und Möglichkeiten der Sprache Java und der Java-Klassenbibliothek erläutert. Dabei wurde aus der mittlerweile riesigen Bibliothek eine Auswahl getroffen, die nach unserer Erfahrung mindestens 90 Prozent aller üblichen Programmierbedürfnisse abdecken sollte. Dies umfasst die vollständige Erläuterung der Sprache Java, einschließlich einer Einführung für Einsteiger und einer ausführlichen Behandlung der objektorientierten Programmierung, sowie die Beschreibung der wesentlichen Aspekte von modernen Java-Anwendungen, insbesondere grafische Oberflächen, Netzwerkkommunikation und Datenbankzugriffe, um nur einige aufzuzählen. Besonderer Wert wurde dabei auf Hunderte von Codebeispielen und direkt kompilier- und ausführbaren Programmen gelegt, die nicht nur die erläuterte Theorie im Praxiseinsatz zeigen, sondern auch gleich als Ausgangsbasis für Ihre Eigen- und Weiterentwicklungen dienen können.
Vorwort
Die Screenshots zu den Beispielen, die Sie im Buch sehen, wurden größtenteils unter Windows Vista erstellt. Die Beispiele wurden aber natürlich auch unter anderen Windows-Versionen und unter Linux getestet. Auf der begleitenden Buch-CD finden Sie neben dem Quellcode der Beispielprogramme alles, was Sie zur Java-Programmierung benötigen: von der neuesten Version des Java-SDKs (für Windows und Linux), über die zugehörige Originaldokumentation und verschiedene Java-Entwicklungsumgebungen bis hin zu der ebenso populären wie leistungsfähigen DatenbankSoftware MySQL, die in den Datenbankkapiteln zum Einsatz kommt. Wir haben das Buch mit großer Sorgfalt und hohem Zeitaufwand erstellt. Dennoch lässt es sich bei einem Werk dieses Umfangs und dieser komplexen Thematik erfahrungsgemäß nie ganz vermeiden, dass sich Tippfehler, irreführende Formulierungen oder gar inhaltliche Fehler einschleichen. Scheuen Sie sich in diesem Fall nicht, uns per E-Mail eine Nachricht1 zukommen zu lassen. Auch für Anregungen oder Themenwünsche (was fehlt, was sollte detaillierter besprochen werden?) sind wir jederzeit dankbar. Etwaige Berichtigungen und Ergänzungen werden wir unter www.carpelibrum.de veröffentlichen. Viel Erfolg und Spaß mit Java wünschen Ihnen die Autoren! Dirk Louis (
[email protected]) Peter Müller (
[email protected]) Neuerungen zu Java 6 Für Leser, die sich schnell über die wichtigsten Neuerungen informieren wollen, hier der Überblick. Tabelle V.1: Neuerungen in Java 6
Neuerung in der Syntax
Beschrieben in Kapitel
Neue Annotationen wurden eingeführt.
41
Neuerung in den Utility-Bibliotheken
Beschrieben in Kapitel
Die neu hinzugekommene Klasse Console erlaubt endlich die Ausgabe von Umlauten auf die Konsole sowie das Einlesen geheimer Daten.
3.5 und 31.4
Neue Methoden in Math bringen mehr Unterstützung zur Manipulation von reellen Zahlen.
6.3
Die String-Klasse besitzt jetzt eine Methode zum Aufspüren leerer Strings.
7.1
Die Arrays-Klasse besitzt jetzt Methoden zum Kopieren und Vergrößern von Arrays.
9.3
Das Spektrum der Collections-Schnittstellen und -Klassen wurde erweitert. 29.3, 29.4 und 29.5
1
30
Bitte unter Angabe von Titel, Auflage und Seite!
Vorwort
Neuerung in den GUI-Bibliotheken
Beschrieben in Kapitel
Ein neuer, sehr empfehlenswerter Swing-Layout-Manager, GroupLay-
19.5
out, wurde eingeführt.
Tabelle V.1: Neuerungen in Java 6 (Forts.)
Für Dialogfenster kann das Modalitätsverhalten nunmehr abgestuft angepasst werden.
22.3
Für die logischen Java-Fonts wurden String-Konstanten definiert.
23.1
Das Double Buffering für Fenster wurde verbessert.
23.2
GIF-Bilder können jetzt auch geschrieben werden.
23.4
Zum Drucken von Texten gibt es jetzt die Methode JTextComponent.print().
25
Die Drag&Drop-Unterstützung wurde verbessert.
26.5
Als »Titel« für die Registerlaschen der JTabbedPane-Komponente können jetzt auch Steuerelemente verwendet werden.
26.1
4
JTable-Tabellen können jetzt auf einfache Weise sortiert und gefiltert
26.2
5
1 2 3
werden. Das Konzept der Startbildschirme (Splash-Screens) wird unterstützt.
26.3
Der Infobereich der Taskleiste (SystemTray) wird unterstützt.
26.4
Neuerung in den weiteren Bibliotheken
Beschrieben in Kapitel
Für die Hintergrundverarbeitung in Swing wurde die Klasse SwingWorker eingeführt.
30.5
6 7 8
Die Klasse File verfügt jetzt auch über Methoden zum Abfragen der Lauf- 31.1 werksbelegung. Mit vergleichsweise wenig Aufwand können jetzt auch Ressourcendateien im XML-Format genutzt werden.
9
32.1
Die eingebettete Java-Derby-Datenbank vereinfacht das Testen von Daten- 33.5 bankanwendungen.
10
Die API für die Webservices-Programmierung wurde in die Standard Edition 43 des JDK übernommen. Neuerung in den Tools
Beschrieben in Kapitel
Einige keytool-Optionen wurden umbenannt.
40.3
Die Angabe der Hauptklasse von JAR-Archiven wurde vereinfacht.
Anhang zu Java-Tools
11
12 13 14
31
Teil 1 Einführung 35
Java war früher eine Eiche
1
43
Einführung für Programmieranfänger
2
79
Das erste Programm
3
105
Java-Stilkonventionen
4
Inhalt
1
Java war früher eine Eiche 1 2
Die Geschichte Javas – nicht die der Insel Java, sondern die der Programmiersprache Java – ist vor allem mit dem Namen eines Mannes verbunden: Bill Gates.1
3
Wir alle kennen Gates als erfolgreichen Geschäftsmann, als Microsoft-Gründer, als liebenswerten Philanthropen und vermutlich reichsten Mann der Welt. Weniger bekannt, zumindest hierzulande, ist dagegen Gates, der Prophet und Visionär, der seine Ideen in Büchern, Zeitungsartikeln, Reden und zahlreichen Interviews verbreitet. Eine dieser Visionen, die der Cäsar der Software-Industrie seit Ende der Neunziger propagiert, kündet von einer schöneren, einer besseren Welt, in der sich unbeseelte, technische Geräte in intelligente, dienstbare Geister verwandelt haben. Da ist von Kühlschränken die Rede, die den eigenen Warenbestand kontrollieren, bei Bedarf selbstständig übers Internet Bestellungen aufgeben und Rezepte vorschlagen. Da erzählt Gates von ausgedehnten Shopping-Touren via TV oder beschwört das Bild von gemütlichen Abenden mit einem guten Rotwein, einem elektronischen Buch, das auf Knopfdruck umblättert, und einem heimeligen Feuer, das als Bildschirmschoner auf dem zentralen Heim-PC prasselt.2
4 5 6 7 8
Wo hat der Mann nur seine seherischen Fähigkeiten her? Wir wissen es nicht. Vermutlich eine Kombination aus immensem Fachwissen, einem wachen Geist und prophetischer Gabe. Wie auch immer, die Idee, dass in der Zukunft immer mehr elektronische Gebrauchsgeräte mit Mikroprozessoren ausgestattet werden könnten, gab es schon früher – beispielsweise bei Sun Microsystems.
9 10 11 12
2
Nur um keine Missverständnisse aufkommen zu lassen: Bill Gates hat Java nicht erfunden – ebenso wenig wie die Sprache Basic, doch dies nur nebenbei –, und Java ist auch kein Produkt der Microsoft Corporation. Java wurde von der Firma Sun Microsystems Inc. entwickelt und hat viele Väter, allen voran James Gosling. 1999 formulierte Gates seine Vision für USA Today wie folgt: »At home, you'll be able to operate your PC by talking to it. It will automatically back up all your information, update its own software and synchronize itself with your TV, cell phone, handheld, and all the devices on your home network. The refrigerator in your kitchen will know how well stocked it is, suggest recipes based on what's available, and order more food from your online grocer. Your TV will double as an interactive shopping mall, letting you buy advertised products or clothes you saw in a sitcom. And if you don't want to watch TV, you'll be able to read an electronic book that knows your favorite authors and automatically downloads their latest novels. If you decide to read one of them, your bank account will be debited.«
13 14
35
Index
1
Java war früher eine Eiche
Das Green-Projekt und die Sprache Oak 1990 war Sun Microsystems auf der Suche nach einem neuen Betätigungsfeld. Das Kerngeschäft mit Workstations3 und Servern lief zwar gut, doch die Konkurrenz des PCs, der immer leistungsfähiger, günstiger und anwenderfreundlicher wurde, war bereits zu spüren. Um langfristig nicht ins Abseits zu geraten, wurde ein Team zusammengestellt, das eruieren sollte, welches die nächsten großen Trends in der Computertechnologie sein könnten. Die streng geheime Untersuchung, die unter dem Namen »Green«-Projekt geführt wurde, kam bald zu dem Ergebnis, dass einer der bedeutendsten Trends die zunehmende Ausstattung von elektronischen Gebrauchsgeräten mit Mikroprozessoren und deren Anbindung an das Internet sei. Ein Trend, der den PC, dieses »Abfallprodukt« der Computerentwicklung, dieses Artefakt im privaten Heim, vollkommen überflüssig machen könnte. Trends und Trendsetter Wenn Firmen Trends für ihren eigenen Geschäftsbereich prognostizieren, dann versuchen sie selbstredend auch, diese Trends nach ihren Vorstellungen zu gestalten. So nimmt es nicht Wunder, dass Sun eine Zukunft ohne PC voraussieht, während Bill Gates, neun Jahre später, den PC (nach wie vor die wichtigste Plattform für Microsoft-Software) in keiner seiner Visionen vergisst, ihn vielmehr als »primus inter non pares« zwischen all den anderen »intelligenten« Geräten sieht. In diesem Zusammenhang sei auch an den »Internet«-PC erinnert, den Sun 1996 vorstellte. Der Internet-PC, der unter dem Produktnamen »JavaStation« verkauft wurde, besaß ein residentes Betriebssystem, jedoch keine Festplatte. Seine Festplatte war das Internet, genauer gesagt spezielle Server im Internet (oder Intranet), die Programme zur Verfügung stellten und auf denen Daten gespeichert werden konnten. Dem Internet-PC war jedoch kein Erfolg beschieden, Sun stellte die Produktion zwei Jahre später ein. Das gleiche Schicksal traf auch das direkte Konkurrenzprodukt zur JavaStation, den NetPC, den Microsoft in Kooperation mit Hewlett Packard und Intel entwickelt hatte – das Vertrauen in den PC war damals offensichtlich nicht allzu groß. Die Mitarbeiter des Green-Projekts gingen nun daran, ein Produkt zu kreieren, das dem prognostizierten Trend nicht nur entsprechen, sondern selbst zum Trendsetter werden sollte: Star7, ein PDA mit Touchscreen, drahtloser Netzanbindung, PCMCIA-Schnittstelle, eigener Unix-Version, einer kleinen animierten Figur namens Duke (siehe Abbildung), die dem Anwender bei der Bedienung des Geräts Hilfestellung leisten sollte, und ... einer eigenen, plattformunabhängigen, robusten, objektorientierten Programmiersprache namens Oak.4
3
36
Leistungsfähige, meist vernetzte Rechner für den professionellen Einsatz, traditionell mit UNIXBetriebssystem, beispielsweise Solaris (von Sun) ausgestattet.
Java war früher eine Eiche
Star7 wurde firmenintern gut aufgenommen, doch nachfolgende Versuche, das Produkt zur Marktreife zu führen, scheiterten. Sun war seiner Zeit einfach zu weit voraus. Das Projekt, für das noch 1992 eine eigene Firma, First Person Inc., gegründet wurde, zerfiel, die Mitarbeiter zerstreuten sich. Aus Oak wird Java
1
Mittlerweile hatte sich im Internet einiges getan. 1993 wurde am Cern-Institut in Lausanne das World Wide Web, das »Netz im Netz«, ins Leben gerufen. Noch im gleichen Jahr entwickelte man am NCSA5 den ersten grafischen Webbrowser, den Mosaic-Browser, an dem auch der Student Marc Andreessen mitarbeitete, der ein Jahr später Netscape gründen und den legendären Netscape Navigator auf den Markt bringen wird.
2 3
Bei Sun hatte man derweil die Entwicklung des World Wide Webs und der Browser aufmerksam verfolgt. Aus Sicht von Sun stellte sich die Situation in etwa wie folgt dar: Auf der einen Seite gab es eine schnell wachsende Gemeinde von Internet-Anwendern, die das Web nutzten und freudig jede Entwicklung begrüßten, die es gestattete, Webseiten lebendiger und interaktiver zu gestalten. Auf der anderen Seite hatte man bei Sun eine leistungsfähige, plattformunabhängige Programmiersprache namens Oak, für die es derzeit keine rechte Verwendung gab, sowie erste Ergebnisse aus einem Projekt, das Sun zum Teil mit finanziert hatte und das sich mit der Entwicklung einer »virtuellen Maschine« beschäftigte, die es Programmierern ermöglichen würde, ihre Anwendungen auf unterschiedlichen Architekturen und Betriebssystemen auszuführen.
4 5 6 7
Wie wäre es nun, wenn man die virtuelle Maschine in einen Browser integrieren und Oak so weiterentwickeln würde, dass Oak-Programme innerhalb dieses Browsers auf beliebigen Plattformen ausgeführt werden könnten? Browser und Programmiersprache könnte man frei im Internet zur Verfügung stellen, um einen neuen Standard zu setzen und später mit Lizenzierungen Gewinn zu erwirtschaften. Darüber hinaus würde man die Attraktivität des Internets erhöhen, was zweifelsohne wiederum den Verkaufszahlen der eigenen Workstations und Server zugute käme, die sich durch hochwertige Netzwerktechnik und beste Vernetzbarkeit auszeichneten. Und vielleicht liebäugelte man auch schon mit der Idee des Internet-PCs (siehe Kasten »Trends und Trendsetter«).
8
Ein neues Team wurde zusammengestellt, dem auch James Gosling wieder angehörte. Aus Oak wurde Java (der Name »Oak« wurde bereits von einer anderen Firma für eines ihrer Produkte verwendet, und so einigte man sich in der Cafeteria, bei viel heißem Kaffee, auf den Namen Java), und 1995 wurde der erste javafähige Browser, HotJava, vorgestellt. Gleichzeitig kün-
12
9 10 11
13 14
4
5
Angeblich soll Oak die Abkürzung für »Object Application Kernel« sein. Dies würde zwar der Vorliebe der Amerikaner für Akronyme entsprechen, doch höchstwahrscheinlich wurde diese höchst prosaische Erklärung erst im Nachhinein gefunden, und der Name stammt von einer Eiche (englisch »Oak«), die James Gosling, der Vater von Oak, von seinem Bürofenster aus sah. National Center für Supercomputing Applications.
37
Java war früher eine Eiche
digte Netscape-Chef Andreessen an, dass er Java für seinen Navigator, den damals unumstrittenen Marktbeherrscher, lizenzieren wird. Und im Januar 1996 wurde das JDK 1.0, die erste Version der Java-Entwicklungstools, zum Download im Internet freigegeben. Der Ansturm auf das JDK war gewaltig und es glich geradezu einem Wunder, dass der Server nicht zusammenbrach. Aber schließlich gehörte Sun ja schon immer zu den führenden Anbietern von High-End-Servern. Java und Bill Gates (eine persönliche Darstellung) Doch nicht überall wurde Java mit Euphorie und Wohlwollen begrüßt, so zum Beispiel im Hause Microsoft. Gerade hatten Gates und Microsoft es geschafft, den ungeliebten Konkurrenten DR DOS durch – so jedenfalls die Vorwürfe des Konkurrenten – absichtlich täuschende Produktvorankündigungen, unlautere Lizenzierungsverfahren, schwarze Listen für Beta-Tester und versteckte Inkompatibilitäten, die verhinderten, dass Windows 3.x zusammen mit DR DOS lief, aus dem Markt zu drängen6, als man erkennen musste, dass man die Entwicklung des Internets verschlafen und seine Bedeutung und Bedrohung unterschätzt hatte. Microsofts Kerngeschäft war der Verkauf von Betriebssystemen und passender Software. Ausgeklügelte Lizenzierungsverfahren mit Computerherstellern sorgten dafür, dass die meisten PCs mit Microsoft-Betriebssystemen ausgeliefert wurden, weit verbreitete Microsoft-Betriebssysteme garantierten eine große Nachfrage nach Microsoft-Anwendersoftware, überall verfügbare Microsoft-Programme erhöhten wiederum die Attraktivität der Microsoft-Betriebssysteme – ein »Engelskreis«. Und nun die Schreckensversion von Millionen abtrünniger PC-Benutzer, die mit dem Netscape-Browser im World Wide Web surfen, die mit Java-Programmen aus dem Internet arbeiten und die es keinen Deut schert, welches Betriebssystem auf ihrem PC läuft, ja die vielleicht sogar nur noch einen Internet-PC besitzen (siehe Kasten »Trends und Trendsetter«). Einmal erwacht, ging Microsoft in die Gegenoffensive: mit eigenem Internet-Portal (MSDN), eigener Webserver-Software (IIS etc.) und eigenem Webbrowser – dem Internet Explorer. Anfangs versuchte Microsoft Java zu ignorieren, doch man erkannte bald, dass es ohne Java-Unterstützung schwierig würde, gegenüber dem Netscape-Browser Marktanteile zu gewinnen. Netscape vertrieb seinen Browser praktisch kostenfrei, gegen Entrichtung einer Registrierungsgebühr, und setzte darauf, dass sich die 6
38
DR DOS war 1989 von der Firma Digital Research entwickelt und als Konkurrenzprodukt zu MS DOS, dem ursprünglichen Microsoft-Betriebssystem (auf dem Windows anfangs nur als ganz normale Anwendung aufgesetzt war), auf den Markt gebracht worden. 1991 wurde Digital Research von dem Netzwerkhersteller Novell übernommen, doch dem Verdrängungskampf des Marktführers konnte man sich nicht erwehren: 1994 wurde die Weiterentwicklung eingestellt. 1996 wurde DR DOS, deren einziger Wert eventuelle Entschädigungszahlungen wegen unlauteren Geschäftsgebarens seitens Microsoft waren, für 400.000 $ an die Firma Caldera verkauft. Im Januar 2000 einigten sich Microsoft und Caldera außergerichtlich.
Java war früher eine Eiche
Popularität des Navigators indirekt auszahlen würde, beispielsweise durch erhöhte Absatzzahlen der Netscape-Server-Software – ein Konzept, das anfangs bestens aufging. Microsoft konterte, indem es Java lizenzierte, den Internet Explorer aufrüstete und ganz offiziell kostenlos im Internet zum Download anbot. Ja, Microsoft belohnte sogar Firmen, die zur weiteren Verbreitung des Internet Explorers beitrugen. Einmal in Schwung gekommen, stellte Microsoft dann auch noch seine Server-Software kostenlos zur Verfügung – ein Schritt, der Netscape besonders empfindlich traf. Der letzte Coup bestand darin, den Internet Explorer zusammen mit dem Betriebssystem Windows 98 auszuliefern. Dies brach dem Navigator endgültig das Genick, und brachte Microsoft eine folgenschwere Klage vor Gericht ein. Wieder einmal wurden die US auf Microsofts Geschäftsgebaren aufmerksam und es kam zum zweiten Antitrust Case »United States v. Microsoft«.
1 2 3 4
U.S. v. Microsoft – der Antitrust Case Bill Gates hat den Erfolg seines Unternehmens einmal damit erklärt, dass zu Zeiten des Goldrausches es ja auch nur wenige Goldsucher zu einem kleinen Vermögen gebracht haben, während die wirklichen Großverdiener die Ausrüster waren, die die Goldsucher mit Hacken, Sieben und anderem Material versorgten. In gleicher Weise würden von der Computer- und ITEuphorie vor allem die Software-Hersteller profitieren.7
5
EXKURS
6 7
Was Gates nicht erwähnte, war, dass der Profit eines Unternehmens natürlich umso größer ist, umso marktbeherrschender seine Stellung ist. Und in diesem Monopoly-Spiel war Microsoft schon immer besonders gut.
8
Monopolstellungen sind eine Sache, der Missbrauch selbiger eine andere. Amerika verfügt über strenge Antitrust-Gesetze, die solchen Missbrauch verhindern sollen. Das erste Antitrust-Gesetz, The Sherman Act, trat 1890 in Kraft. 20 Jahre später wurde das Gesetz auf den Standard Oil-Trust des Magnaten John D. Rockefeller8 angewandt und Standard Oil wurde in sechs Teile aufgespalten. Spektakuläre Antitrust-Fälle neuerer Zeit waren die versuchte, aber misslungene Zerschlagung von IBM sowie die erfolgreiche Zerschlagung des Telefonmonopols des Telekommunikationsriesen AT&T Mitte der Achtziger.
9 10 11
Microsoft geriet erstmals 1994 in Konflikt mit den Antitrust-Gesetzen. Es kam jedoch zu keiner Anklage, da man sich bereits im Vorfeld mit dem Department of Justice einigen konnte.
12 13 14
7
8
Mit ähnlichen Argumenten warb die Post zur Zeit des aufkommenden E-Commerce für ihre Aktie: »Wenn Sie Waren bei einem Online-Anbieter bestellen, wer stellt Ihnen dann die Ware zu? Genau, die Post!« Rockefeller war zu diesem Zeitpunkt etwa zweimal so vermögend wie Gates.
39
Java war früher eine Eiche
Da sich die Geschäftspraktiken des Konzerns aber nicht wesentlich änderten, reichten das Department of Justice und 18 US-Staaten im Jahre 1998 erneut Klage ein. Zu den Vorwürfen gehörten: – –
Aushandeln von Exklusivabkommen Bündelung eigenständiger Produkte, namentlich den Vertrieb des Internet Explorers als Teil der Windows 95/98-Betriebssysteme (später wurde die Liste erweitert, da Microsoft immer weitere Anwenderprogramme mit seinen Betriebssystemen kombinierte und dadurch Anbieter vergleichbarer Produkte, die der Anwender einzeln hätte zukaufen müssen, schädigte)
–
Aufrechterhaltung einer Monopolstellung auf dem PC-Markt
–
Versuch, auf dem Browser-Markt eine Monopolstellung zu erreichen (was Microsoft mittlerweile trotz Verurteilung auch geglückt ist)
Begleitet wurde der Antitrust-Case von privaten Antitrust-Klagen geschädigter Konkurrenten (allen voran Netscape, später sollte Sun folgen) und Kunden (Microsoft hatte das Update von Windows 95 auf Windows 98 in Amerika für 89 Dollar verkauft. Als sich im Laufe der Gerichtsverhandlungen herausstellte, das Microsoft intern zuerst einen Preis von 49 Dollar in Erwägung gezogen hatte, lag die Vermutung nahe, dass Microsoft seine Monopolstellung zum Durchdrücken überzogener Preise ausnutzte.) Im Januar 2000 trat Bill Gates als Unternehmenschef zurück, vermutlich um den Konzern zu retten und sich selbst ein wenig aus der Schusslinie zu nehmen. Doch Richter Thomas Penfold Jackson, der den Antitrust-Case leitete, ließ sich nicht beeindrucken. Im Juni 2000 erging das Urteil, dass der Konzern in zwei Teile aufgespalten werden sollte, einen für die Windows-Betriebssysteme und einen zweiten für die Anwendersoftware (Software, Internet Explorer etc.), sodass Microsoft in der Zukunft seine Vormachtstellung auf dem Betriebssystemmarkt nicht mehr zur Promotion seiner Anwendersoftware ausnutzen kann. 2001, unter der Bush-Regierung, wurde das Urteil einstweilig aufgehoben und an einen anderen Richter übergeben. Danach begannen Verhandlungen für eine gütliche Einigung. Zurück zu Java. Der Browser-Krieg war kaum in Gang gekommen, da eröffnete Microsoft eine zweite Front und wandte sich Java zu. Das erste Geschoss, das Gates auf Java abfeuerte, trug die Aufschrift ActiveX und erwies sich als wenig durchschlagskräftig. ActiveX war keine eigene Programmiersprache, sondern lediglich eine Technologie zur Erstellung von Software-Komponenten, die in Webseiten und Programme eingebettet werden konnten – vornehmlich also Konkurrenz für die beiden JavaTechnologien der Java-Applets und der JavaBeans. Im Vergleich zu diesen
40
Java war früher eine Eiche
war ActiveX jedoch kompliziert zu programmieren, plattformgebunden und unsicher in der Verwendung (zumindest für den unbedarften Websurfer, der sich ActiveX-Elemente aus Webseiten auf seine lokale Festplatte lud). Microsofts zweites Geschoss war schon gewichtiger. Zuerst lancierte Microsoft die integrierte Entwicklungsumgebung Visual J++ für Java, dann stattete es diese Umgebung mit einer eigenen Java Virtual Machine9 aus. Microsoft rechtfertigte dies damit, dass die eigene Virtual Machine besser sei als die Sun-Machine und man seinen Kunden nur das Beste bieten wolle. Sun sah darin einen Versuch, den Java-Standard aufzuweichen und eine zweite zum Sun-Standard inkompatible Java-Laufzeitumgebung zu etablieren. Um die Integrität der Java-Plattform zu erhalten, strengte Sun eine Gerichtsklage an. Der Prozess verlief gut für Sun und im Januar 2001 konnte man Microsoft einen außergerichtlichen Frieden diktieren. Microsoft durfte weiter die Sun Java-Technologie lizenzieren und unverändert verwenden, musste aber die Weiterentwicklung seiner eigenen Microsoft Virtual Machine aufgeben und die Auslieferung bis zum Jahr 2004 gänzlich einstellen.
1 2 3 4 5
C#, .NET und XP Die eigene Java Virtual Machine war Microsoft untersagt worden, doch den Konzern traf dies nur marginal. Arbeitete man doch bereits an einer neuen Virtual Machine, diesmal nicht für Java, sondern für eine eigene Programmiersprache: C#.
6 7
C# ist Java sehr ähnlich, und ebenso wie Java-Code wird C#-Code nicht in maschinenspezifische Befehle, sondern in virtuellen Objektcode übersetzt. Wenn ein C#-Programm gestartet wird, lädt die Virtual Machine (die Laufzeitumgebung) den Objektcode, wandelt ihn in maschinenspezifischen Code um und führt ihn aus.
8 9
Die »Virtual Machine« für C# heißt .NET-Framework. Ähnlich wie die Java Virtual Machine besteht das .NET-Framework in der Hauptsache aus Werkzeugen zum Ausführen der Programme (Interpreter, Garbage Collector etc.) sowie einer umfangreichen Klassenbibliothek, auf die der Programmierer beim Schreiben seiner Programme zurückgreifen kann.
10 11
Im Gegensatz zur Java Machine wird das .NET-Framework jedoch nur für Windows-Plattformen angeboten und es unterstützt nicht nur eine, sondern eine Vielzahl von Programmiersprachen. Welche Sprachen unterstützt werden? Alle Sprachen, für die ein .NET-Compiler verfügbar ist, der den Quelltext in den virtuellen Objektcode des Frameworks übersetzt. Gehört Java auch zu diesen Sprachen? Nein, nicht soweit uns bekannt ist. Microsoft stellt aber Programme zur Verfügung, mit denen Java-Quelltext in C#-Code umgewandelt werden kann. Ist es sinnvoll, seine Java-Programme in C++9
12 13 14
Die Virtual Machine ist der Kern der Laufzeitumgebung, in der Java-Programme ausgeführt werden. Ein Java-Programm kann auf jedem Rechner ausgeführt werden, auf dem eine Java Virtual Machine installiert ist (weitere Informationen zu diesem Punkt finden Sie in Kapitel 3.1).
41
Java war früher eine Eiche
Programme umzuwandeln? Nur, wenn Sie von Java auf C# umsteigen möchten. Und sollte man auf C# umsteigen? Ist C# besser als Java? Wird C# Java verdrängen? C# und .NET sind zweifelsohne eine starke Konkurrenz zu Java, doch sie werden Java nicht verdrängen. Beide, Java wie C#, haben ihre Vorzüge und auch ihre Nachteile, Stärken wie Schwächen, und beide werden sich im Konkurrenzkampf weiterentwickeln und reifen. Ein versöhnliches Wort zum Schluss Ehre, wem Ehre gebührt. Gates ist ein genialer Geschäftsmann, auch wenn man nicht alle seine Geschäftspraktiken gutheißen mag. Wir, oder zumindest die meisten von uns, lieben seine Produkte und nutzen sie tagtäglich (nicht weil er oder Microsoft sie uns aufgedrängt haben, sondern weil wir sie anderen, vergleichbaren Produkten vorgezogen haben). Gates wird immer ein Synonym für den Microsoft-Konzern sein, ob als Unternehmenschef oder seit Januar 2000 als Chairman und Chief Software Architect. C# und .NET sind hervorragende Technologien, denen sicherlich eine große Zukunft bevorstehen wird – ebenso wie Java. Freuen wir uns auf eine Zeit, in der es Platz für Java- und C#-Programmierer gibt und in der man guten Gewissens beide Sprachen beherrschen kann.
42
Inhalt
2
Einführung für Programmieranfänger 1
2 Dieses Kapitel ist für Programmierneulinge gedacht und führt anhand theoretischer Gedankenspiele und -modelle in die Grundprinzipien der Programmierung ein. Es wird hier jedoch noch nichts programmiert! Wer schon programmiert hat oder sehr ungeduldig ist, möge gleich zu Kapitel 3 springen.
3
TIPP
4
Java ist eine wunderbare Programmiersprache. Sie ist modern, leistungsfähig, vielseitig, in gewisser Weise sogar ästhetisch ... Eines ist sie jedoch ganz bestimmt nicht: leicht zu erlernen. Nun, das alleine wäre ja noch gar nicht mal so schlimm – schließlich erwartet ja niemand, dass eine so mächtige Sprache von einem Tag auf den anderen zu meistern wäre. Während sich der hoffnungsvolle Novize jedoch in die meisten anderen Sprachen Schritt für Schritt einarbeiten kann, muss er sich in Java gleich zu Beginn mit einer Vielzahl sehr komplizierter Konzepte auseinandersetzen.
5 6 7
So besteht bereits das einfachste Programm, das in Java geschrieben werden kann, aus mindestens vier Zeilen, vollgepackt mit kryptischem Code:
8
public class Programm { public static void main(String[] args) { System.out.println("Hallo Anwender!"); } }
9 10
Das gleiche Programm, das übrigens nichts anderes macht, als den Benutzer mit einem freundlichen »Hallo« auf der Konsole zu begrüßen, hätte ein Basic-Programmierer Anfang der Neunziger wie folgt geschrieben:
11
print "Hallo Anwender!"
Die erste Hürde liegt für den Java-Programmierer also extrem hoch, sodass ihm letzten Endes nur drei Möglichkeiten bleiben:
12
1.
Er läuft unter der Hürde durch, d.h., er akzeptiert die ihm nicht verständlichen Syntaxformen des Grundgerüsts als Gott gegeben und arbeitet sich danach so gut es geht schrittweise in die Sprache ein. 2. Er wartet mit dem Programmieren, bis er sich theoretisch so weit in die Sprache eingearbeitet hat, dass er sämtliche im Grundgerüst vorkommenden Konzepte verinnerlicht hat. Dann erst nimmt er die Hürde. 3. Er verzichtet auf jegliche Eleganz und klettert über die Hürde.
13
43
Index
14
Einführung für Programmieranfänger
Den letzten Weg werden Sie jetzt einschlagen. Indem Sie in einem kurzen Abriss die historische Entwicklung der Programmiersprachen von den ersten Anfängen bis zu Java nachvollziehen, werden Sie sich mit den wichtigsten Konzepten moderner Programmiersprachen zumindest soweit vertraut machen, dass Sie das Grundgerüst verstehen und einfache Programme schreiben können.
2.1
Vom Maschinencode zu den höheren Programmiersprachen
Programmieren bedeutet, dem Rechner Befehle zu erteilen, die er ausführen kann. Früher wurden diese Befehle vom Rechenwerk ausgeführt, das in den ersten Rechenmaschinen aus dem 19. Jahrhundert noch mechanisch arbeitete. Heute ist das Herzstück des Rechners der Prozessor. Doch eines hat sich nicht geändert: Jeder Rechner/Prozessor verfügt nur über einen ganz begrenzten Satz an Befehlen, und auch diese versteht er nur, wenn sie binär, d. h. als eine Folge von Nullen und Einsen, codiert vorliegen.1 10100001 00111111101001100110010100000001 10000011 1100000 00000000000000000000000000000011 10100011 00111111101001100110010100000001
2.1.1
Assembler
Da die Programmierung mit binär codierten Maschinenbefehlen äußerst mühselig und fehleranfällig ist, sann man schon früh auf Möglichkeiten, die Programmierung zu vereinfachen. Ein erster Schritt in diese Richtung war Assembler. Assembler-Programmierung ist immer noch Programmierung auf Maschinenebene, nur dass der Programmierer die Befehle nicht mehr binär codiert, sondern in Form sogenannter »Mnemonics« niederschreibt. MOV EAX, [3FA66501] ADD EAX, 3 MOV [3FA66501], EAX
Ein spezielles Übersetzerprogramm, das ebenso wie die Sprache Assembler genannt wird, übersetzt die Befehlsnamen (MOV, ADD ...), Registerbezeichnungen (EAX, EDX ...) Speicheradressen ([3FA66501] …) und Zahlen (3 …) in die zugehörigen Binärfolgen.
1
44
Ein moderner Maschinenbefehl sieht vereinfacht etwa wie folgt aus: 10000011 1100000 00000011 10000011 ist der Befehlscode des Addieren-Befehls. Er weist den Prozessor an, den Wert des im ersten Operanden angegebenen Registers um den Wert des zweiten Operanden zu erhöhen. Die Bitfolge 1100000 steht für das Register EAX (Register sind einzelne, interne Speicherzellen des Prozessors, in denen er die zu verarbeitenden Daten, etwa aus dem Arbeitsspeicher geladene Daten, zwischenspeichern kann). Der zweite Operand ist eine einfache Ganzzahl, die 3, nur eben binär codiert im Dualsystem: 00000011. Der gesamte Befehl lautet demnach: »Addiere zu dem aktuellen Wert des Registers EAX den Wert 3.«
Vom Maschinencode zu den höheren Programmiersprachen
2.1.2
Das Variablenkonzept
Einfache Zahlen können zumeist direkt in die Maschinenbefehle integriert und vom Prozessor verarbeitet werden (siehe den Befehl ADD EAX, 3). Für komplexere Daten wie z. B. Zeichenfolgen (»Dies ist ein Text«) ist dies nicht möglich. Sie müssen im Arbeitsspeicher abgelegt und bei Bedarf in den Prozessor geladen werden.
1
Zeichenfolgen werden in der Programmierung gemeinhin als Strings bezeichnet.
2
INFO
Auch Daten, denen im Programm eine bestimmte Bedeutung zukommt und deren Wert sich im Laufe des Programms ändern kann, müssen im Arbeitsspeicher verwahrt werden. Ein Programms, das die Mausklicks des Anwenders zählt, könnte beispielsweise einen bestimmten Bereich im Arbeitsspeicher für die Anzahl der Mausklicks reservieren. Beim Programmstart schreibt es den Wert 0 in diesen Speicherbereich. Danach lauscht es auf Mausklicks, und jedes Mal, wenn der Anwender eine Maustaste drückt, liest es den aktuellen Wert des Speicherbereichs in den Prozessor, addiert den Wert 1 und schreibt das Ergebnis zurück in den Speicherbereich. So werden aus an sich bedeutungslosen Zahlenwerten sinnvolle Daten!
3 4 5 6
In den obigen Maschinenbefehlen wurden die Speicheradressen, von denen Daten geladen oder in die Daten geschrieben werden sollten, stets als explizite (hexadezimale oder binäre) Adressen angegeben. Dies hat zwei gravierende Nachteile: Der Programmierer muss sich merken oder gesondert notieren, welche Daten an welchen Adressen stehen, und er ist selbst für die korrekte Speicherbelegung verantwortlich, muss also beispielsweise aufpassen, dass sich die Speicherbereiche zweier Daten nicht überlappen.
0xEF01
0xEF01
0xEF01
0xEF02
0xEF02
0xEF02
0xEF03
0xEF03 Preis
0xEF04
0xEF04
0xEF05
0xEF05
5
7 8 9 Abbildung 2.1: Variablen sind ein wenig wie Schubladen, in denen man Werte aufbewahren kann
10 11 12
0xEF03 Preis
13 0xEF05
14
45
Einführung für Programmieranfänger
Hier bringt das Konzept der Variablen, das schnell auch in Assembler Einzug fand, Abhilfe. Statt numerischer Speicheradressen vergibt der Programmierer Namen und überlässt dem Übersetzerprogramm (im Falle der Assembler-Programmierung also dem Assembler) die Zuordnung dieser Namen zu echten Speicheradressen. Statt nichts sagender Zahlen kann der Programmierer nun Namen auswählen, die auf Art und Zweck der gespeicherten Daten hinweisen, beispielsweise mausklickZaehler, alter oder preis.
INFO
Das Beziehungsgefüge aus Name, zugehörigem Speicherort und darin abgelegten Daten wird als Variable bezeichnet. Im alltäglichen Sprachgebrauch wird die Variable aber auch häufig mit ihrem Wert, d. h. mit den in ihr abgelegten Daten, gleichgesetzt. Wenn ein Programmierer also davon spricht, dass er die Variable A zur Variablen B addiert, so meint er damit, dass der Wert in der Variablen A zu dem Wert der Variablen B hinzuaddiert wird.
2.1.3
Die höheren Programmiersprachen
Ende der Fünfziger, Anfang der Sechziger wurden die ersten höheren Programmiersprachen entwickelt. Anders als die Assemblersprachen, bei denen jeder Befehl der Sprache exakt einem Maschinenbefehl entspricht, arbeiten die höheren Programmiersprachen mit Anweisungen, die weitaus komplexere Operationen ausführen und in eine ganze Folge von Maschinenbefehlen übersetzt werden. Klar, dass es hierfür spezielle, entsprechend leistungsfähige Übersetzerprogramme geben muss. Dabei wird je nach der prinzipiellen Arbeitsweise zwischen Compilern und Interpretern unterschieden: ■
■
Ein Compiler liest den gesamten Quelltext des Programms (oder Programmmoduls) ein, analysiert ihn und übersetzt ihn dann in Maschinencode. Dieser Maschinencode kann mithilfe eines weiteren Programms, des Linkers, mit anderem Maschinencode verbunden und in ein ausführbares Programm verwandelt werden (beispielsweise eine Windows-EXEDatei). Zum Starten des Programms wird die ausführbare Programmdatei aufgerufen. Ein Interpreter liest den Quelltext Zeile für Zeile ein. Jede Zeile wird sofort übersetzt und direkt zur Ausführung an den Prozessor weitergereicht. Zum Ausführen des Programms muss also nur der Quelltext des Programms an den Interpreter übergeben werden.
Für jede höhere Programmiersprache gibt es einen eigenen Compiler oder Interpreter. Wortschatz und Grammatik der Programmiersprache sind in der Spezifikation der Sprache niedergeschrieben und im Compiler (oder Interpreter) implementiert. Die Sprache wird also genauso gut durch den Compiler (Interpreter) wie durch ihre Spezifikation definiert. Oder um es noch drastischer zu formulieren: Wenn Sie ein Programm geschrieben haben, das sich mit dem zugehörigen Compiler nicht übersetzen lässt, nutzt es Ihnen gar nichts, wenn Sie belegen können, dass Ihr Quelltext exakt den 46
Vom Maschinencode zu den höheren Programmiersprachen
Regeln der Sprachspezifikation folgt. Letzte Instanz ist in so einem Fall der Compiler. Sie können sich bei dem Compiler-Hersteller beschweren, aber wenn Sie Ihr Programm übersetzen möchten, müssen Sie den Regeln folgen, die der Compiler implementiert2. Die obige Unterscheidung von Compiler und Interpreter beschreibt die traditionellen Grundprinzipien, die mit Kompilation und Interpretation verbunden sind. Obwohl diese Grundprinzipien nach wie vor gelten, können moderne Compiler und Interpreter in einzelnen Punkten davon abweichen. (Der JavaCompiler erzeugt beispielsweise weder echten Maschinencode noch ausführbare EXE-Dateien. Dazu später in Kapitel 3.1 mehr.)
1 TIPP
2 3
Ein einfaches Programm, das von der Konsole eine Radiusangabe einliest, den zugehörigen Kreisumfang berechnet und diesen auf die Konsole ausgibt, würde in Basic geschrieben, wie folgt aussehen:
4
Listing 2.1: Ein Basic-Programm, wie es in den späten Achtzigern geschrieben worden sein könnte ' Programm zur Berechnung des Kreisumfangs print "Geben Sie einen Radius ein: " input radius umfang = 2 * 3.1415 * radius print "Der Kreisumfang betraegt " umfang
5 6
Das Programm beginnt damit, dass es einen Text ausgibt, der den Anwender auffordert, über die Tastatur einen Radiuswert einzutippen. Dieser wird mit dem Schlüsselwort input in die Variable radius eingelesen. (Die Variable radius wird dabei automatisch eingerichtet und mit ausreichend Speicher verbunden.) In der vorletzten Quelltextzeile wird der Kreisumfang berechnet und in der neuen Variablen umfang abgespeichert. Schließlich gibt das Programm einen kurzen erläuternden Text und den Inhalt von umfang auf die Konsole aus.
7 8 9
Das Beispiel zeigt nicht nur, wie einfach die Programmierung in höheren Programmiersprachen sein kann; es demonstriert auch den Einsatz einiger wichtiger Elemente, die für Quelltexte höherer Programmiersprachen typisch sind: ■
■
10 11
Kommentare, die den Quelltext erklären und vom Programmierer eingefügt werden, damit er selbst oder andere Programmierer sich später schneller in den Quelltext einarbeiten können. Kommentare werden vom Übersetzerprogramm ignoriert. (' Programm zur Berechnung des Kreisumfangs). Konstante Werte, die in der Programmierung als Literale bezeichnet werden ("Geben Sie einen Radius ein! ", 2, 3.1415 ...).
12 13 14
2
Ärgerlich ist es, wenn es zu einer Programmiersprache mehrere Compiler (bzw. Interpreter) gibt, die die Syntax und Grammatik der Sprache jeder für sich ein wenig verändern oder eigenmächtig interpretieren. In so einem Fall schafft jeder dieser Compiler (Interpreter) einen eigenen, inkompatiblen Sprachdialekt (wie dies Microsoft beispielsweise mit der Einführung seines Visual J++- Compilers anstrebte).
47
Einführung für Programmieranfänger
Beachten Sie, dass in Dezimalzahlen die Nachkommastellen durch einen Punkt – und nicht wie im Deutschen üblich durch ein Komma – abgetrennt werden! ■ ■
■
Variablen, zum Abspeichern und Verwalten der Daten (radius, umfang). Operatoren, zur Durchführung einfacher, grundlegender Bearbeitungsschritte wie der Multiplikation (*) oder der Zuweisung eines berechneten Werts an eine Variable (=). In der Sprache oder in den Standardbibliotheken der Sprache implementierte höhere Befehle, die häufig benötigte Aufgaben erledigen – beispielsweise das Einlesen von Daten über die Tastatur (input) oder die Ausgabe von Strings auf die Konsole (print).
Bildschirmausgaben und Konsolenanwendungen EXKURS
Die meisten PC-Benutzer, vor allem Windows- oder KDE-Anwender, sind daran gewöhnt, dass die Programme als Fenster auf dem Bildschirm erscheinen. Dies erfordert aber, dass das Programm mit dem Fenstermanager des Betriebssystems kommuniziert und spezielle Optionen und Funktionen des Betriebssystems nutzt. Programme, die ohne fensterbasierte, grafische Benutzeroberfläche (GUI = graphical user interface) auskommen, können hierauf jedoch verzichten und stattdessen die Konsole zum Datenaustausch mit dem Benutzer verwenden. Die Konsole ist eine spezielle Umgebung, die dem Programm vorgaukelt, es lebe in der guten alten Zeit, als es noch keine Window-Systeme gab und immer nur ein Programm zurzeit ausgeführt werden konnte. Dieses Programm konnte dann uneingeschränkt über alle Ressourcen des Rechners verfügen – beispielsweise die Tastatur, das wichtigste Eingabegerät, oder auch den Bildschirm, das wichtigste Ausgabegerät. Der Bildschirm war in der Regel in den Textmodus geschaltet, wurde also nicht aus Pixelreihen, sondern aus Textzeilen aufgebaut. Unter Windows heißt die Konsole MS-DOS-Eingabeaufforderung oder auch nur Eingabeaufforderung und kann je nach Betriebssystem über START/PROGRAMME oder START/PROGRAMME/ZUBEHÖR aufgerufen werden. Konsolenprogramme werden meist von einem Konsolenfenster aus aufgerufen, d.h., am Eingabeprompt (Ende der untersten Textzeile der Konsole) wird der Name des Programms eingetippt und mit der (Enter)-Taste abgeschickt. Die Ausgaben des Programms erscheinen Zeile für Zeile darunter (sind die Zeilen der Konsole vollgeschrieben, werden sie nach oben gescrollt). Eingaben über die Tastatur werden in der aktuellen Zeile der Konsole angezeigt und nach Drücken der (Enter)-Taste an das Programm weitergeleitet.
48
Vom Maschinencode zu den höheren Programmiersprachen
Abbildung 2.2: Ausführung eines Java-Programms auf der WindowsKonsole
1
2 3 4
2.1.4
Kontrollstrukturen 5
Maschinenbefehle werden grundsätzlich der Reihe nach – so wie sie in den Arbeitsspeicher geladen wurden oder wie der Interpreter sie an den Prozessor schickt – ausgeführt. Wo es nötig oder opportun ist, von dieser sequenziellen Ausführung abzuweichen, kann der Programmierer Sprungbefehle einbauen, die dafür sorgen, dass die Programmausführung an einer beliebigen anderen Stelle des Programms fortgeführt wird.
6 7
Höhere Programmiersprachen, auch Basic, geben die Fähigkeit Sprünge zu vollziehen meist auf verschiedene Weise an den Programmierer weiter: ■ ■ ■
8
durch direkte Sprünge zu beliebigen Anweisungen (das zugehörige Schlüsselwort heißt in der Regel goto, durch spezielle Kontrollstrukturen, die der Übersetzer mithilfe von Sprüngen realisiert, durch Funktionsaufrufe (siehe Abschnitt »Funktionen«).
9 10
Die direkten Sprünge mit goto sollen uns nicht weiter interessieren. Sie führen schnell zu wirrem Spagetticode und werden in Java daher nicht unterstützt. Wesentlich interessanter sind da schon die Kontrollstrukturen, zu denen ■ ■ ■
11
die bedingte Ausführung, die Verzweigung und die Schleife
12 13
gehören. Die bedingte Ausführung
14
Eine bedingte Ausführung dient dazu zu entscheiden, ob eine nachfolgende Anweisung oder ein Anweisungsblock ausgeführt werden soll oder nicht. In
49
Einführung für Programmieranfänger
den meisten Programmiersprachen wird die bedingte Ausführung mit dem Schlüsselwort if eingeleitet. In Basic hat sie folgenden Aufbau: if (Bedingung) then Anweisung(en) end if
Diese Konstruktion kann man wie einen deutschen Konditionalsatz lesen: »Wenn die Bedingung erfüllt ist, dann (und nur dann) führe die Anweisungen aus.« Bei Ausführung des Programms wird zuerst die Bedingung ausgewertet. Die Bedingung ist dabei nichts anderes als ein Vergleich, beispielsweise verkaufszahlen < 1000 (»ist der Wert von verkaufszahlen kleiner 1000?«). Liefert der Vergleich als Ergebnis wahr, ist die Bedingung erfüllt und der zur if-Bedingung gehörende Anweisungsblock wird ausgeführt; andernfalls wird das Programm mit der nächsten Anweisung unter der if-Anweisung (d. h. unter der Zeile end if) fortgesetzt. Die meisten Programmiersprachen stellen für das Aufsetzen von Vergleichen die folgenden Vergleichsoperatoren zur Verfügung (siehe Tabelle 2.1). Tabelle 2.1: Gängige Vergleichsoperatoren
Operator
Bedeutung
Beispiel (für i = 3 und j = 50)
==
gleich
i == 4
// unwahr
!=
ungleich
i != 4
// wahr
<
kleiner
i < j
// wahr
>
größer
i > j
// unwahr
= 3
// wahr
Die Verzweigung Eine Verzweigung bedeutet, dass das Programm in Abhängigkeit vom Wert einer Bedingung einen von mehreren Anweisungsblöcken ausführen soll. Die einfachste Verzweigung liegt vor, wenn man eine if-Bedingung um einen else-Zweig erweitert: if (Bedingung) then Anweisungen A else Anweisungen B end if
Diese Konstruktion kann man wie folgt lesen: »Wenn die Bedingung erfüllt ist, dann führe die Anweisungen A aus, überspringe den else-Block mit den Anweisungen B und fahre mit der nächsten
50
Vom Maschinencode zu den höheren Programmiersprachen
Anweisung hinter der if-else-Konstruktion fort. Wenn die Bedingung nicht erfüllt ist, überspringe den Block mit den Anweisungen A, führe den elseBlock mit den Anweisungen B aus, und fahre mit der nächsten Anweisung hinter der if-else-Konstruktion fort.« Eine weitere wichtige Form der Verzweigung ist die select- oder case-Verzweigung, die in Abhängigkeit vom Wert eines Ausdrucks zu verschiedenen Stellen in einem Anweisungsblock springt.
1 INFO
Die Schleife
2
Schleifen dienen dazu, eine Anweisung oder einen Anweisungsblock mehrfach hintereinander ausführen zu lassen. Die wichtigsten Schleifentypen sind die for- und die while-Schleife.
3
Typisch für Schleifen ist die Einrichtung einer Schleifenvariablen, über die man kontrolliert, wie oft die Schleife ausgeführt wird. Dazu wird die Schleifenvariable bei Eintritt in die Schleife auf einen Anfangswert gesetzt, in der Schleife in Einerschritten erhöht (oder in irgendeiner anderen Weise verändert) und vor jedem neuen Schleifendurchgang getestet.
4 5
i = 1 do while (i 10) { eineVar = "Hallo"; } zweiteVar = eineVar;
Welcher Wert wird hier in zweiteVar abgespeichert? Die Zahl 3 oder der String »Hallo«? Ohne den Wert von n zu kennen, kann diese Frage nicht beantwortet werden – von uns ebenso wenig wie von dem Compiler. Ein Interpreter könnte diesen Quelltext dagegen ohne Probleme übersetzen, da er den Code Schritt für Schritt übersetzt und ausführt. Er muss nur irgendwo protokollieren, welche Variable gerade welche Art von Daten enthält (und diese Information immer dann anpassen, wenn in einer Variablen ein Wert eines anderen Datentyps abgespeichert wird).
TIPP
–
Bei der Programmierung stellt sich oftmals die Frage, wann eine bestimmte Information (etwa der Datentyp einer Variablen) verfügbar und bekannt ist. Unterschieden wird hierbei zwischen der Laufzeit (während der Ausführung des Programms) und der Kompilierzeit (vor der Ausführung des Programms; also zu einem Zeitpunkt, wo nur der reine, noch zu kompilierende Quelltext vorliegt). Typisierte Variablen Damit ein Programm kompilierbar ist, müssen die Datentypen der Variablen und Werte (weitgehend) zur Kompilierzeit bekannt sein. Kompilierte Programmiersprachen wie C oder Java fordern daher vom Programmierer, dass er Variablen, mit denen er arbeiten möchte, vorab deklariert. In der Deklaration sind der Name und der Datentyp der Variablen (bzw. der Werte, die in der Variablen gespeichert werden können) anzugeben. Gültige Variablendeklarationen in C sind beispielsweise: int zaehler; float quotient; char zeichen; int wert = 3; char option = 'b'; float bruch = 0.33333;
// Deklaration mit gleichzeitiger // Wertzuweisung (Initialisierung)
int, float und char sind Schlüsselwörter5 von C, die speziell für die Variablendeklaration in die Sprache aufgenommen wurden.
4
5
54
Das Codefragment wurde bereits ein wenig an die C- (und Java-) typische Syntax angepasst: Anweisungen werden mit Semikolon abgeschlossen! – Anweisungsblöcke, etwa von if-Bedingungen werden in geschweifte Klammern eingefasst! – Kommentare, die bis zum Zeilenende reichen, werden mit // eingeleitet. (Kommentare, die über mehrere Zeilen gehen, beginnen mit /* und enden mit */.) Wörter, die zum festen Wortschatz der Sprache gehören.
Die strukturierte Programmierung
Fortan kann der Programmierer nur noch mit Variablen arbeiten, die deklariert sind. Versucht er, neue Variablen wie bisher direkt zu verwenden, quittiert der Compiler dies bei der Übersetzung des Quelltextes mit einer Fehlermeldung. zahl = 3; quadr = zahl * zahl;
// Fehler: zahl nicht definiert // Fehler: quadr nicht definiert
1
Korrekt wäre: int zahl; int quadrat;
2
zahl = 3; quadr = zahl * zahl;
3
In gleicher Weise schmettert der Compiler Versuche ab, in einer Variablen einen Wert abzuspeichern, der nicht zum Datentyp der Variablen passt: int zahl; zahl = 3.4;
4
/* Fehler: zahl ist vom Typ int, zugewiesen wird aber ein Gleitkommaliteral */
5
Korrigierte Version:
6
float zahl; zahl = 3.4;
7
Positive Folgen der Typisierung ■
■
Der Compiler stellt sicher, dass Werte und Variablen eines Datentyps nur in einer typgemäßen Weise verwendet werden. (Sie beispielsweise also nicht Strings miteinander multiplizieren.) Keine Fehler durch falsch geschriebene Namen.
8 9
Wenn Sie einen Variablennamen falsch schreiben, können Sie froh sein, wenn Sie mit einem Compiler arbeiten. 10
float prozentsatz = 0.012; float guthaben = 2000; float gewinn = guthaben * prozentsatz;
11
Ein Interpreter, der Variablen nach Bedarf erzeugt, wird prozentsatz für eine neue Variable halten, die er diensteifrig anlegt und deren Wert er mit guthaben multipliziert. Welchen Wert hat prozentsatz? Entweder weist der Interpreter der neu angelegten Variablen einen Standardwert zu, beispielsweise 0, oder er interpretiert einfach das zufällige Bitmuster, das er in der neu angelegten Variablen vorfindet, als Wert. Auf jeden Fall wird der Wert nicht dem Wert in prozentsatz entsprechen und das Programm wird falsche Ergebnisse liefern.
12 13 14
Ein Compiler wird dagegen sofort feststellen, dass der Name prozentsatz nicht deklariert ist und eine Fehlermeldung ausgeben.
55
Einführung für Programmieranfänger
■
Einfachere und effizientere Speicherverwaltung. Auch wenn Sie in höheren Programmiersprachen mit Integern, Gleitkommazahlen, Strings und anderen Daten arbeiten, dürfen wir nicht vergessen, dass alle diese Daten vom Übersetzer in Bitfolgen codiert werden. Für jeden Datentyp gibt es dazu ein eigenes Codierungsverfahren: für Integer beispielsweise das 2n+1-Komplement, für Gleitkommazahlen die IEEE 754, für Zeichen den Unicode, Strings werden meist als Folgen von Zeichen codiert. Für die elementaren Datentypen (Integer, Gleitkomma, Zeichen, Boolean, nicht aber String!) liefern diese Codierungsverfahren Bitfolgen fester Größe. Steht der Datentyp einer Variablen von vornherein fest, kann der Übersetzer dem Datentyp entnehmen, wie viel Speicher er für die Variable reservieren muss. Dieses sehr effiziente und speicherschonende Verfahren ist typisch für Compiler. Interpreter, die Datentypwechsel gestatten, müssen den benötigten Speicher hingegen von vornherein so berechnen, dass er für alle Datentypen ausreicht, oder dynamisch, d. h. zur Laufzeit, bei jedem Datentypwechsel neuen Speicher reservieren.
■
Übersichtliche Zusammenfassung der Variablendeklarationen. Variablendeklarationen müssen nicht an dem Ort stehen, wo die Variable erstmalig verwendet wird. Sie müssen der Verwendung lediglich vorangestellt sein. Der Programmierer kann dies dazu nutzen, wichtige Variablen am Anfang des Programms zusammenzuziehen. Die Programme werden dadurch meist übersichtlicher und besser verständlich. Lediglich lokal benötigte Hilfsvariablen, beispielsweise Schleifenvariablen, werden vor Ort deklariert.
»Negative « Folgen der Typisierung Die strenge Abgrenzung der Datentypen, die vom Compiler überwacht wird, führt zu besseren, weil sichereren Programmen. Sie kann sich aber auch als extrem störend und nervtötend erweisen – dann nämlich, wenn der Programmierer darauf angewiesen ist, einen Wert von einem Datentyp in einen anderen umzuwandeln. Betrachten wir noch einmal das Basic-Programm aus dem Abschnitt »Die höheren Programmiersprachen«: ' Programm zur Berechnung des Kreisumfangs print "Geben Sie einen Radius ein: " input radius umfang = 2 * 3.1415 * radius print "Der Kreisumfang betraegt " umfang
In der dritten Zeile liest das Programm eine Eingabe über die Tastatur ein und speichert diese in der Variablen radius. Eingaben von der Tastatur sind aber stets Zeichenfolgen (Strings). Wenn der Anwender also beispielsweise 10 eintippt, so schickt er nicht die Zahl 10, sondern den String "10" an das Programm! Der Basic-Interpreter macht radius daher zu einer String-Variablen. 56
Die strukturierte Programmierung
Eine Zeile darunter taucht radius in einer Multiplikation auf. Die Multiplikation ist aber nur für Zahlen, nicht für Strings möglich. Der Basic-Interpreter merkt dies und wandelt den String "10" automatisch in die Integer-Zahl 10 um und rechnet mit dieser weiter. In der letzten Zeile haben wir schließlich die umgekehrte Typwandlung: in umfang steht eine Gleitkommazahl, die vom Interpreter für die Ausgabe auf die Konsole in einen String umgewandelt wird.
1
Ein Compiler würde eine solche Vorgehensweise nie unterstützen. Zuerst würde er vom Programmierer fordern, dass radius als String-Variable und umfang als float-Variable deklariert werden müssen. Kommt der Programmierer dieser Aufforderung nach, bemängelt der Compiler, dass die StringVariable radius nicht in Multiplikationen und die float-Variable umfang nicht in Ausgaben verwendet werden kann.
2 3 4
Typumwandlungen Das obige Beispiel verdeutlicht wohl eindrücklich, dass eine sinnvolle Programmierung ohne die Möglichkeit zur Umwandlung von Datentypen nicht denkbar ist. Kompilierte und interpretierte Sprachen unterscheiden sich aber zumeist darin, welche Art von Typumwandlungen sie zulassen und wie diese durchgeführt werden.
5 6
So erlauben die meisten interpretierten Programmiersprachen die Umwandlung des Typs von Variablen, die kompilierten Programmiersprachen hingegen nur die Umwandlung von Werten.
7
int eineZahl = 3; float andereZahl; andereZahl = eineZahl;
8
Hier wird in der dritten Zeile der Wert 3 aus der int-Variablen ausgelesen, in einen float-Wert umgewandelt (3.0) und in der float-Variablen andereZahl abgespeichert. eineZahl enthält danach immer noch den int-Wert 3!
9 10
Weiterhin gibt es in streng typisierten, kompilierten Sprachen nur wenige Typumwandlungen, die automatisch durchgeführt werden. (Ein Beispiel für eine automatische Typumwandlung in C ist die im obigen Beispiel demonstrierte Umwandlung eines int-Werts in einen float-Wert.)
11
Bestimmte Typumwandlungen können vom Programmierer durch Voranstellung des gewünschten Zieltyps erzwungen werden – beispielsweise die Umwandlung eines float-Werts in einen int-Wert (wobei die Nachkommastellen verloren gehen).
12 13
int eineZahl; float andereZahl = 3.4; eineZahl = (int) andereZahl;
14
57
Einführung für Programmieranfänger
Schließlich gibt es die Möglichkeit, die gewünschte Typumwandlung selbst zu programmieren. Diese Möglichkeit steht selbstverständlich immer offen, kann sich aber als recht mühsam erweisen. Manchmal hat der Programmierer aber auch Glück und es gibt in der Standardbibliothek passende Funktionen, die die gewünschte Typumwandlung implementieren.
2.2.2
Funktionen
Funktionen sind ein von vielen Programmiersprachen angebotenes Hilfsmittel zur Modularisierung des Programmcodes (in rein objektorientierten Sprachen wie Java treten an die Stelle der Funktionen die Klassen und deren Methoden, doch dazu später mehr). Warum empfiehlt es sich, Programmcode zu modularisieren? Erstens wird der Programmquelltext übersichtlicher. Wenn Sie ein mittelgroßes Programm von einigen Hundert Zeilen, Anweisung für Anweisung aufsetzen, werden Sie irgendwann große Schwierigkeiten haben zu verstehen, was in Ihrem Programm eigentlich vorgeht (noch schwieriger dürfte dies für andere Programmierer sein, die Ihr Programm später eventuell überarbeiten und warten müssen). Mithilfe von Funktionen können Sie größere Programme in Teilprobleme auflösen. Nehmen wir an, Sie möchten ein Programm schreiben, das mehrere Zahlenwerte einlesen und daraus den Mittelwert berechnen soll. Anstatt den Code direkt Anweisung für Anweisung niederzuschreiben, können Sie das Programm in die folgenden drei Teilprobleme auflösen: Werte einlesen Mittelwert berechnen Ergebnis ausgeben
Nachdem Sie dies getan haben, schreiben Sie für jedes der drei Teilprobleme eine eigene Funktion, die das Teilproblem bearbeitet. Im Hauptteil des Programms brauchen Sie dann nur noch nacheinander die drei Funktionen aufzurufen. Der zweite Grund, der für die Auslagerung von Code in Funktionen spricht, ist, dass man Funktionen sehr gut wiederverwerten kann. Eine einmal im Programm definierte Funktion kann man nämlich an jeder beliebigen Stelle des Programms aufrufen. Stellen Sie sich vor, Sie müssen in Ihrem Programm an verschiedenen Stellen eine recht komplizierte mathematische Formel berechnen. Ohne Funktionen müssten Sie an jeder Stelle, an der die Formel berechnet werden soll, die Anweisungen zur Berechnung der Formel neu aufsetzen. (Sie können den Code natürlich kopieren, doch wenn Sie später im Code einen Fehler bemerken, haben Sie immer noch das Problem, nachträglich sämtliche Stellen aufsuchen und den Fehler beheben zu müssen). Mit Funktionen können Sie eine Funktion zur Berechnung der Formel schreiben und brauchen diese dann nur noch an den betreffenden Stellen aufzurufen.
58
Die strukturierte Programmierung
Definition und Aufruf Letzten Endes ist eine Funktion nichts anderes als ein Anweisungsblock, der mit einem Namen (dem Funktionsnamen) versehen ist, damit man ihn von beliebigen Stellen des Programms aus aufrufen kann. funktionsname() { Anweisung(en); }
1
Weiter unten im Programm könnte diese Funktion dann wie folgt aufgerufen werden:
2
... funktionsname(); ...
3
Der Funktionsaufruf bewirkt einfach, dass die Programmausführung in den Anweisungsblock der Funktion springt. Nach Abarbeitung des Funktionscodes kehrt die Programmausführung dann wieder in die Zeile des Aufrufs zurück und das Programm wird fortgesetzt (natürlich ohne nochmalige Ausführung der Funktion).
4 5
Funktionen wären aber nicht sehr hilfreich, wenn es nur darum ginge, einen ausgelagerten Anweisungsblock auszuführen. Darum gestatten Funktionen es auch, Werte vom Aufrufer entgegenzunehmen und Ergebnisse an den Aufrufer zurückzuliefern. Betrachten Sie hierzu die folgende C-Funktion, die einen int-Wert entgegennimmt und das Quadrat dieses Werts zurückliefert. int quadrat(int n) int ergebnis;
6 7
{
8
ergebnis = n * n; return ergebnis;
9
}
Der Name dieser Funktion ist quadrat. Sie definiert einen int-Parameter namens n, über den sie beim Aufruf einen int-Wert als Argument entgegennimmt. Dann folgt der Anweisungsblock der Funktion, in dem zuerst eine lokale Variable ergebnis deklariert wird. Diese Variable kann nur innerhalb der Funktion quadrat verwendet werden.
10
In der nächsten Zeile wird das Quadrat des übergebenen int-Werts berechnet (n * n) und in der lokalen Variablen ergebnis abgespeichert. In der letzten Anweisung wird das berechnete Quadrat an den Aufrufer zurückgeliefert.
12
Um einen Wert zurückliefern zu können, muss man in der Funktionsdefinition vor dem Funktionsnamen den Datentyp des zurückgelieferten Werts angeben (in unserem Beispiel int) und im Anweisungsblock der Funktion den Wert mithilfe des Schlüsselworts return abschicken.
13
11
14
59
Einführung für Programmieranfänger
HALT
Das Schlüsselwort return liefert nicht nur den übergebenen Wert zurück, sondern beendet auch die Funktion. Nachfolgende Anweisungen werden also nicht mehr ausgeführt.
TIPP
Soll eine Funktion keinen Rückgabewert zurückliefern, gibt man in der Funktionsdefinition als Rückgabetyp void an und verzichtet auf die returnAnweisung. void eine_funktion() { ... }
Sehen wir uns noch an, wie eine solche Funktion aufgerufen wird. main() als Startpunkt des Programms
Wenn Sie ein Programm aufrufen, wird es Anweisung für Anweisung ausgeführt. Das bedeutet aber nicht, dass die erste ausgeführte Anweisung auch die oberste Anweisung im Programmquelltext ist. Die meisten Programmiersprachen definieren vielmehr spezielle Eintrittspunkte. In C ist dies beispielsweise eine besondere Funktion namens int main(). Jedes CProgramm muss über eine solche main()-Funktion verfügen, und mit dem Aufruf dieser Funktion beginnt die Ausführung des Programms. Listing 2.2: Ein vollständiges C-Programm, das den Gebrauch von Funktionen illustriert 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20
#include int quadrat(int n) { int ergebnis; ergebnis = n * n; return (ergebnis); } int main() { int loop; int erg; for (loop=1; loop = 0) { radius = r; } } public double umfang() { return 2 * 3.14159 * radius;
13 14
73
Einführung für Programmieranfänger
} public double flaeche() { return 3.14159 * radius * radius; } } ... // in gleicher Quelldatei Kreis k1 = new Kreis(); k.radius = 12;
// Fehler! Zugriff auf private-Element
Natürlich muss der Autor der Klasse, wenn er ein Element als private deklariert, auch dafür sorgen, dass das Element über zugängliche (in der Regel public-) Methoden sinnvolle Werte zugewiesen bekommen und sein Wert gegebenenfalls auch abgefragt werden kann. In obiger Implementierung der Klasse Kreis kann der Nutzer der Klasse8 den Konstruktor Kreis(double r) verwenden, um seine Kreis-Objekte von Anfang an sinnvolle Radien zuzuweisen. Tatsächlich muss er sogar diesen Konstruktor verwenden, da er sonst keine Möglichkeit hat, den Radius eines Kreis-Objekts zu ändern. Die Implementierung des Konstruktors, sprich die if-Bedingung, stellt sicher, dass dem Radius keine negativen Werte zugewiesen werden können. (Bei Übergabe eines negativen Werts an r wird die Zuweisung an radius nicht ausgeführt. radius behält den Standardwert 0, mit dem der Compiler alle Felder einer Klasse automatisch initialisiert.) Abfragen kann der Programmierer den Wert des Feldes radius überhaupt nicht, er kann sich lediglich von den public-Methoden umfang() und flaeche() die auf dem Radius basierenden Werte für Kreisumfang und -fläche zurückliefern lassen. Wollte der Autor der Klasse das Abfragen oder nachträgliche Ändern des radius-Werts gestatten, müsste er dazu entsprechende public-Methoden definieren. class Kreis { ... double getRadius() { return radius; } void setRadius(double r) { if (r >= 0) { radius = r; } } ... }
8
74
Der Programmierer, der Objekte der Klasse erzeugt und verwendet (Kann durchaus mit dem Autor der Klasse identisch sein.).
Die objektorientierte Programmierung
Statische Elemente Die vornehmste Bestimmung einer Klasse ist, dass aus ihr Objekte erzeugt werden, dass ihre Felder als Instanzvariablen an die Objekte weitergegeben werden und dass ihre Methoden zur Programmierung mit den Objekten verwendet werden. Doch in einer rein objektorientierten Programmiersprache müssen Klassen auch anderen Zwecken dienen – beispielsweise als Funktionensammlung.
1
In Abschnitt »Funktionen« haben Sie die Funktionen als ein vorzügliches Mittel kennengelernt, um Teilprobleme modular zu lösen. Wenn Sie in Java Teilprobleme modular lösen möchten, müssen Sie dazu Methoden implementieren. Dies hat zwei Nachteile: Erstens müssen Sie, um diese Methoden aufrufen zu können, immer erst ein Objekt der Klasse erzeugen. Zweitens besteht die Möglichkeit, ja es ist sogar wahrscheinlich, dass die Felder und Methoden einer solchen Klasse gar keine sinnvollen Objekte bilden (zumindest keine Gebilde, die nach menschlicher Vorstellung als »Objekte« tituliert werden könnten).
2 3 4 5
Ein gutes Beispiel hierfür ist die Java-Klasse Math, die dem Programmierer eine Reihe von wichtigen mathematischen Methoden und Konstanten zur Verfügung stellt. Damit der Programmierer die Methoden und Konstanten der Klasse verwenden kann, ohne erst ein (ansonsten sinn- und nutzloses) Objekt der Klasse erzeugen zu müssen, sind sämtliche Elemente der Klasse als static deklariert.
6 7
// aus Math public static double tan(double a) { return StrictMath.tan(a); // default impl. delegates to StrictMath }
8
Das Schlüsselwort static teilt dem Compiler mit, dass die so deklarierten Elemente nicht Teil der Objekte der Klasse sind, sondern allein im Besitz der Klasse bleiben und daher direkt über den Namen der Klasse aufgerufen werden können.
9 10
// Berechnung einer Mauerhöhe aus Winkel und Entfernung double winkel = 30; // in Grad double entfernung = 12.0; // in Meter double hoehe = entfernung * Math.tan(Math.toRadians(winkel));
11
In einer Klassendefinition können statische und nicht-statische Elemente kombiniert werden.
12 TIPP
13 14
75
Einführung für Programmieranfänger
Die Eintrittsmethode main() Auch der Programmeintrittspunkt main(), der in C oder C++ als Funktion definiert ist, wurde in Java durch eine statische Methode ersetzt. public class Programm { public static void main(String[] args) { // Hier beginnt die Programmausführung } }
Vererbung Zu guter Letzt sei noch auf ein fortgeschrittenes, aber sehr wichtiges Konzept der objektorientierten Programmierung hingewiesen: die Vererbung. Die Vererbung gestattet es, neue Klassen auf der Basis bereits vorhandener Klassen zu definieren. class Basis { int basisFeld; void eineMethode() { basisFeld = 12; } } class Abgeleitet extends Basis { int abgFeld; void andereMethode() { eineMethode(); abgFeld = basisFeld; } }
Die neue, abgeleitete Klasse erbt sämtliche Elemente der nach dem Schlüsselwort extends angegebenen Basisklasse und kann – soweit es die in der Basisklasse vergebenen Zugriffsspezifizierer erlauben – auf die geerbten Elemente zugreifen. Und auch der Nutzer der abgeleiteten Klasse kann – wiederum sofern es die in der Basisklasse vergebenen Zugriffsspezifizierer erlauben – über die Objekte auf die geerbten Elemente zugreifen: Abgeleitet a = new Abgeleitet(); a.eineMethode();
Die abgeleitete Klasse erbt immer alle Elemente der Basisklasse (mit Ausnahme der Konstruktoren); sie kann nicht selbst entscheiden, welche Elemente sie erben möchte und welche nicht. Immerhin aber kann die abgeleitete Klasse geerbte Methoden überschreiben, indem sie eine eigene Methode definiert, die den gleichen Namen wie eine geerbte Methode trägt: class Abgeleitet extends Basis { int abgFeld;
76
Noch Fragen?
void eineMethode() { basisFeld = 112; } void andereMethode() { eineMethode(); abgFeld = basisFeld; }
// abgFeld = 112!
1
}
2.4
2
Noch Fragen?
Nach dieser doch recht umfangreich gewordenen Einführung sind Sie gewappnet, sich in den nachfolgenden Kapiteln dieses Buchs ernsthaft in die Programmierung mit Java einzuarbeiten.
3
Sollten Sie nicht alles verstanden haben oder angesichts der Fülle an vorgestellten Konzepten, Begriffen und Syntaxformen zu verwirrt sein, um Ihren derzeitigen Wissenstand klar beurteilen zu können, so seien Sie versichert, dass Sie bestimmt weit mehr erfasst und verstanden haben, als Ihnen jetzt womöglich bewusst ist. Lesen Sie einfach weiter, beginnen Sie selbst in Java zu programmieren, und Sie werden feststellen, dass Sie gute Fortschritte machen. Und sollten Sie doch einmal Verständnisfragen haben, die sich auf grundlegende Konzepte beziehen und in den weiteren Kapiteln des Buches nicht geklärt werden, blättern Sie einfach hierher zurück (wiewohl eine Wiederholung des Stoffes dieses Kapitels nach Abschluss des Grundlagenteils – vor Lektüre des Teils zur objektorientierten Programmierung – nur zu Ihrem Vorteil gereichen kann). Sie möchten noch mehr über die Grundlagen der Programmiersprachen erfahren: Eine noch ausführlichere Einführung in die Grundprinzipien der Programmierung und die Geschichte der Programmiersprachen finden Sie auf unserer Website www.carpelibrum.de.
4 5 6 7 8 9 TIPP
10 11 12 13 14
77
Inhalt
3
Das erste Programm 1 2
Die wichtigste Aufgabe des Programmierers, die Tätigkeit, die (hoffentlich) den größten Teil seiner Arbeitszeit ausmacht, die ihn ebenso begeistert wie ermüdet, die ihn höchste Ekstase und tiefste Depressionen durchleben lässt, die ihm Fluch und Leidenschaft ist – das ist das Aufsetzen der Programmquelltexte. Die nachfolgende Umwandlung dieser Quelltexte in ausführbare Programme ist dagegen nur eine Formalität. Immerhin, sie muss gemacht werden. Wie dies geht und welche Hilfsmittel Ihnen dabei zur Verfügung stehen, ist Thema dieses Kapitels.
4 5
Programmerstellung in Java
6
Java-Programme müssen kompiliert und interpretiert werden. Umsteigern von rein kompilierten (C++, Pascal ...) oder rein interpretierten Sprachen (Perl ...) mag dies umständlich erscheinen, doch die Gründe liegen auf der Hand: Durch geschickte Kombination können die Vorteile beider Techniken genutzt werden.
3.1.1
7 8
Portierbarkeit und Robustheit
9
Java blickt auf eine recht bewegte Entwicklungsphase zurück, in der es schon zu manchen Verwerfungen, Kehrtwendungen und Neuorientierungen kam. Treu geblieben ist sich die Sprache aber stets in dem Ziel, eine Sprache für Programme in Netzwerken zu sein.
10
Anfangs – zu Zeiten von Oak – sollten dies Anwendungen sein, die vernetzte Verbrauchergeräte intelligenter machen, später – als aus Oak Java wurde – kam die erfolgreiche Neuausrichtung auf das World Wide Web und die Erstellung von Programmen für Webseiten (Applets). Heute können Sie mit Java »normale« Anwendungen, über Netzwerke verteilte Anwendungen, Applets und Programme für technische Kleingeräte (beispielsweise Handys und PDAs) schreiben.
11 12 13
Da Netzwerke nicht selten Endgeräte mit den unterschiedlichsten Plattformen (Kombination von Prozessor und Betriebssystem) verbinden, war von vornherein klar, dass Java interpretiert werden musste.
14
79
Index
3.1
3
Das erste Programm
Plattformunabhängigkeit durch Interpretation EXKURS
Compiler übersetzen den Quelltext eines Programms traditionell in prozessor- und betriebssystemspezifischen Maschinencode. Meist ist dies der Maschinencode des aktuellen Systems, auf dem der Programmierer seinen Code erstellt.1 Stellen Sie sich nun vor, Sie würden mit einem solchen Compiler ein Applet für die Intel/Windows-Plattform erstellen. Das Applet betten Sie in eine Webseite ein und veröffentlichten diese im Web. In kürzester Zeit würden Sie eine große Zahl »begeisterter« E-Mail-Zuschriften von Webbesuchern erhalten, die die Webseite mit dem Applet-Code auf ihren Rechner heruntergeladen haben und, da sie keinen Intel/Windows-Rechner verwenden, statt der Ausgabe des Applets nur eine leere Fläche sehen – vielleicht noch mit einer Fehlermeldung, dass der Appletcode auf diesem Rechner nicht ausgeführt werden kann. Die Lösung dieses Problems liegt darin, den Quelltext erst auf dem Rechner des Anwenders (hier der Websurfer) in plattformspezifischen Maschinencode zu übersetzen. Dies leistet der Interpreter. Voraussetzung ist natürlich, dass auf dem System des Anwenders ein passender Interpreter installiert ist. Der Java-Interpreter ist ein kleines Programm namens java, das es für alle wichtigen Plattformen gibt. Allein mit dem Java-Interpreter können JavaProgramme aber noch nicht ausgeführt werden. Die Java-Standardbibliothek Software, die über CD oder DVD ausgeliefert wird, unterliegt praktisch keinerlei Größenbeschränkung. Wenn Sie jedoch Programme schreiben, die der Anwender vor jeder Ausführung über das Internet herunterlädt, wie es beispielsweise bei den Applets der Fall ist, wird die Codegröße zu einem ganz entscheidenden Faktor. Wie aber lässt sich die Codegröße klein halten? Heute gehört zu praktisch jeder Programmiersprache eine umfangreiche Standardbibliothek mit vordefinierten Funktionen oder Klassen, auf die der Programmierer in seinen Quelltexten zurückgreifen kann. Die Java-Standardbibliothek enthält beispielsweise Klassen für die Ein- und Ausgabe, für mathematische Berechnungen, für die GUI-Programmierung und vieles mehr. In traditionellen kompilierten Sprachen wird der Bibliothekscode, den der Programmierer in seinem Quelltext verwendet, bei der Programmerstellung in der Regel in die Programmdatei eingebunden. Je mehr Bibliothekselemente der Programmierer verwendet, umso größer wird die Programmdatei. Um diese »unnötige« Aufplusterung der Programmdatei zu vermeiden, wird in Java der Bibliothekscode erst vom Interpreter eingebunden. Dies setzt
1
80
Will der Programmierer seine Programme für andere Plattformen erstellen, muss er dazu Cross-Compiler (laufen auf einer Plattform, kompilieren für eine andere) verwenden oder sich für jede Plattform einen eigenen Entwicklungsrechner einrichten.
Programmerstellung in Java
natürlich voraus, dass die Standardbibliothek zusammen mit dem JavaInterpreter auf dem Rechner des Anwenders installiert ist. Robustheit Gibt es fehlerfreie Programme? Denkt man an die Produkte mancher Software-Häuser, so möchte man dies bezweifeln. Doch woran liegt es, dass hundertfach getestete Programme immer noch Fehlfunktionen aufweisen, ja sogar abstürzen? Die Gründe sind verschieden, müssen nicht einmal im Verantwortungsbereich der Programmierer liegen. Auf jeden Fall aber spielt die zunehmende Komplexität der Programme eine entscheidende Rolle – und die Frage, inwieweit die Sprache dem Programmierer hilft, mit wachsender Komplexität fertig zu werden.
1 2
3
Javas Meinung zu diesem Punkt könnte klarer nicht sein: Die Verantwortung für eine sichere, fehlerfreie Programmierung kann nicht allein dem Programmierer überlassen werden, die Sprache selbst muss den Programmierer soweit es geht entlasten, bzw. ihn zu sicherer, defensiver Programmierung anhalten.
4 5
Java setzt dies vorbildlich um. Nicht nur durch die konsequente Objektorientierung, die Absicherung der Bibliotheksmethoden durch Exceptions, die strenge Typisierung, sondern gerade und vor allem auch durch die automatische dynamische Speicherverwaltung – eine stete Fehlerquelle –, die nahezu vollständig aus den Händen des Programmierers in den Verantwortungsbereich des Interpreters gegeben wurde. Javas dynamische Speicherverwaltung – Objekte werden dynamisch reserviert, der Zugriff auf die Objekte erfolgt über Referenzen, Objekte, auf die keine Referenzen verweisen, werden von der automatischen Speicherbereinigung, der »Garbage Collection« entsorgt – geht übrigens auf die objektorientierte Sprache Smalltalk zurück.
6 7 8 INFO
9 10
Die Java-Laufzeitumgebung Das Gesamtpaket aus erweitertem Interpreter (die Java Virtual Machine, s. u.), der Java-Standardbibliothek (runtime classes), dem Java-Plugin für die Ausführung von Applets in Browsern sowie einigen weiteren Hilfsprogrammen, die zum Starten und Ausführen von Java-Code benötigt werden, bezeichnet man als die Java-Laufzeitumgebung (Java Runtime Environment, kurz JRE).
11 12
Die JRE gibt es für eine Vielzahl von Plattformen und kann kostenlos von der Sun-Java-Website heruntergeladen werden. Eine aktuelle Version ist auch im JDK enthalten.
13
Sun hat selbstredend großes Interesse daran, dass die JRE auf möglichst jedem Rechner vorhanden ist (sodass Anwender Java-Programme und Applets problemlos ausführen können). Software-Entwickler dürfen die JRE daher frei, aber bitte unverändert, mit ihren Java-Programmen an die
14
81
Das erste Programm
Anwender weitergeben und auf deren System installieren. Bemühungen, die JRE automatisch mit den Windows-Betriebssystemen auszuliefern, sind vorerst gescheitert. Immerhin wurde Microsoft gerichtlich untersagt, sein Betriebssystem mit einer eigenen (womöglich inkompatiblen) JRE zu vertreiben.
3.1.2
Effizienz und Schutz geistigen Eigentums
Traditionell ist die Interpretation mit zwei schwer wiegenden Nachteilen verbunden: ■
■
Die Programmausführung wird verlangsamt, da der Interpreter die gerade auszuführenden Codeabschnitte immer erst noch übersetzen muss. Das Programm wird als Quelltext an den Anwender übergeben. Das Know-how, das womöglich in dem Quelltext steckt, ist dadurch ungeschützt und kann leicht kopiert werden.
Kompilierte Sprachen kennen diese Probleme nicht, da die Übersetzung komplett vor der Auslieferung des Programms erfolgt und das Programm nicht als Quelltext, sondern als binärer Maschinencode vertrieben wird. Um nun die Vorzüge der Kompilation mit der Notwendigkeit der Interpretation zu verbinden, geht Java einen Zwischenweg: der Java-Quelltext wird kompiliert und interpretiert.
3.1.3
Das Java-Modell und die Virtual Machine
Nachdem der Programmierer den Java-Quelltext fertig gestellt hat, übersetzt er ihn mithilfe des Java-Compilers javac – allerdings nicht in den Maschinencode eines bestimmten Prozessors, sondern in plattformunabhängigen Bytecode. Die resultierenden Class-Dateien (der Compiler schreibt den Bytecode jeder übersetzten Klassendefinition in eine eigene Datei mit der Extension .class) stellen das fertige Programm dar und werden vom Programmierer an die Anwender verteilt. Der Anwender, auf dessen System die passende JRE installiert ist (oder von der Setup-Routine des erworbenen Programms installiert wird), führt das Programm mithilfe des Interpreters und den anderen Teilen der JRE aus. Der Interpreter übersetzt den Bytecode, beginnend mit der main()-Methode des Programms, schrittweise in plattformspezifischen Maschinencode und lässt diesen ausführen. Die Interpretation des vorkompilierten Bytecodes ist zwar immer noch langsamer als die direkte Ausführung optimierten Maschinencodes, auf jeden Fall aber um einiges schneller als die Interpretation von reinem Quelltext. Moderne Java-Interpreter, die sogenannten Just-In-Time-Compiler (JITter) beschleunigen die Programmausführung, indem sie oft benötigte Programmteile in optimierten Maschinencode übersetzen und im Arbeitsspeicher halten, 82
Installation des JDK
sodass bei Wiederverwendung während der aktuellen Programmausführung keine erneute Übersetzung notwendig ist. Zusätzlich werden fortlaufend HotSpot-Analysen gemacht, d.h., sehr oft durchlaufene Codeabschnitte (die »HotSpots«) werden parallel zur Programmausführung intensiv optimiert und dadurch noch schneller. Manche Entwicklungsumgebungen bieten darüber hinaus auch Compiler an, die den Bytecode komplett in Maschinencode übersetzen (wie ein konventioneller C++-Compiler beispielsweise), sodass zur Ausführung gar kein Java-Interpreter mehr benötigt wird (auf Windows-Systemen hat man dann eine normale .exe-Datei vorliegen).
1 2
Der erweiterte Java-Interpreter führt nicht nur den Bytecode aus, er reserviert auch den vom Programm benötigten dynamischen Speicher und gibt nicht weiter benötigten Speicher wieder frei (Speicherbereinigung). Aus Sicht des generierten Bytecodes gleicht er einem virtuellen Rechner, auf dem der Bytecode ausgeführt wird. Der erweiterte Java-Interpreter wird daher auch gemeinhin als die Java Virtual Machine (JVM) bezeichnet. Der Bytecode ist demnach der Maschinencode der Java Virtual Machine.
3 4 5
3.2
Installation des JDK 6
Um Java-Programme erstellen und testen zu können, benötigen Sie grundsätzlich nichts weiter als das Java SE Development Kit (Java-SDK oder kurz JDK). Die zum Zeitpunkt der Drucklegung dieses Buches aktuelle Version, Java SE 6, finden Sie auf der Buch-CD. Ansonsten können Sie sich neuere wie ältere Versionen für die verschiedenen Plattformen von der Java-Site herunterladen (http://java.sun.com/javase/downloads/index.jsp2). Das JDK enthält sämtliche Tools, die Sie für die Java-Programmierung benötigen.
7 8
Die JDK-Tools sind fast ausnahmslos Konsolenanwendungen, deren Bedienung dem einen oder anderen Leser antiquiert vorkommen wird. Wer lieber mit integrierten Entwicklungsumgebungen arbeitet, der findet im Internet eine Reihe von leistungsfähigen Java-Entwicklungsumgebungen namhafter Software-Häuser, von denen es zumeist auch kostenlose Download-Versionen gibt: ■ ■ ■
10 11
Eclipse von IBM (www.eclipse.org) NetBeans von Sun, Nachfolger von Sun ONE (www.netbeans.org) JBuilder von Borland (www.borland.com/de/products/jbuilder)
In der Regel schaffen wir es, die eine oder andere Entwicklungsumgebung (als kostenlose Voll- oder Demoversion) mit auf die Buch-CD zu packen. Dort finden Sie dann auch eine PDF-Datei mit Hinweisen zur Installation und Bedienung der jeweiligen Entwicklungsumgebungen.
2
9
12 13 TIPP
14
Beachten Sie, dass sich die Verzeichnisstruktur von Websites ändern kann. Falls Sie unter /javase/downloads/index.jsp nichts mehr finden sollten, beginnen Sie einfach mit dem Home-Verzeichnis java.sun.com und suchen Sie sich Ihren Weg zum JDK.
83
Das erste Programm
Ältere JDK-Versionen Ältere JDK-Versionen benötigen Sie, wenn Sie Java-Programme erstellen möchten, die von älteren Virtual Machines ausgeführt werden sollen. Wenn Sie beispielsweise mit dem aktuellen JDK Applets für Webseiten schreiben, kann es passieren, dass diese in den Browsern der Webbesucher nicht angezeigt werden, weil diese nicht das aktuelle Java-Plug-In verwenden. Um daraus resultierenden Beschwerde-Mails zu entgehen, können Sie entweder einen Link zum Download des aktuellen Plug-Ins anbieten oder Ihr Applet gleich für eine ältere Virtual Machine kompilieren. Installieren Sie hierzu die ältere JDK-Version parallel zu ihrer aktuellen Version und führen Sie eine Cross-Kompilierung durch (siehe Anhang zu JDKTools).
3.2.1
Installation
Die Installation des Java Development Kit (JDK) ist relativ einfach. Nachdem Sie das Paket von der Buch-CD kopiert oder von Suns Webserver (java.sun.com) heruntergeladen haben, müssen Sie die selbst extrahierende Datei nur noch ausführen und die Fragen zur Lizenz und zur Konfiguration der Installation beantworten. Windows Führen Sie die EXE-Setupdatei aus (über den Dialog START/AUSFÜHREN oder durch Doppelklick auf die Datei im Windows Explorer). Abbildung 3.1: Auswahl der zu installierenden Komponenten
84
Installation des JDK
Lassen Sie das JDK möglichst vollständig installieren (Voreinstellung, siehe Abbildung 3.1) und gestatten Sie dem Setup-Programm auch die Konfiguration Ihrer Browser zu aktualisieren. Wenn Sie nicht genügend Festplattenspeicher zur Verfügung haben, deaktivieren Sie die Option PUBLIC JRE. Der SDK (Option DEVELOPMENT TOOLS) enthält eine eigene private Version der JRE, die zum Testen der Programme verwendet werden kann. Die Optionen SOURCE CODE und DEMOS können Sie bei Speichermangel ebenfalls deaktivieren. Allerdings spart dies relativ wenig Mbytes, und insbesondere die Quelldateien der Java-API sind für fortgeschrittene Java-Programmierer eine wertvolle Referenzquelle!
1
TIPP
2
3
Linux Legen Sie ein Verzeichnis an, unter dem das JDK installiert werden soll (beispielsweise Java) und kopieren Sie in dieses die BIN-Datei. Danach öffnen Sie ein Konsolenfenster, wechseln in Ihr Verzeichnis und lassen die BINDatei ausführen.
4 5
Die Konfiguration der Installation ist unter Linux derzeit nicht möglich. Abbildung 3.2: Start der Installation unter Linux/KDE
6 7 8 9 10 11
Deinstallation Wenn Sie das JDK irgendwann wieder deinstallieren möchten, rufen Sie die mitgelieferte Deinstallationsroutine über die SYSTEMSTEUERUNG, Symbol SOFTWARE auf.
12 13
Wenn Sie für Ihre Browser statt des neu installierten Plug-Ins lieber Ihr altes Plug-In verwenden wollen, klicken Sie in der Systemsteuerung auf das JAVASymbol und wählen dort das gewünschte Plug-In aus.
14
Linux-Anwender löschen einfach das Verzeichnis der JDK-Installation.
85
Das erste Programm
3.2.2
Anpassen des Systems
Wenn die Installation erfolgreich abgeschlossen worden ist, ist ihre Festplatte um ca. 120 bis knapp 300 Mbyte (je nach den gewählten Installationsoptionen) ärmer und Sie um das JDK der neuesten Version reicher. Jetzt müssen Sie Ihr System nur noch so konfigurieren, dass Sie mit den JDK-Tools bequem arbeiten können. Erweiterung des Systempfads Die Java-Tools (insbesondere javac und java) werden über die Konsole aufgerufen. Wenn Sie dabei nicht immer dem Programmnamen den vollständigen Pfad zur EXE-Datei des Programms voranstellen wollen: Konsolenprompt:> c:\Compiler\Java\jdk1.6.0\bin\javac
müssen Sie den Pfad zu den JDK-Tools in den Systempfad des Betriebssystems einfügen. Nehmen wir an, dass Sie das JDK in das Verzeichnis c:\Compiler\Java\jdk1.6.0 (Windows) bzw. /home/ihrname/Java/jdk1.6.0 (Linux) installiert haben. ■
Windows 95/98: Legen Sie zur Sicherheit eine Kopie der Datei c:\autoexec.bat an und laden Sie die Datei danach in einen Editor (beispielsweise Notepad, Aufruf über START/PROGRAMME/ZUBEHÖR/EDITOR oder über START/AUSFÜHREN, NOTEPAD eingeben und abschicken). Suchen Sie nach einem PATH-Eintrag und fügen Sie das BIN-Verzeichnis der JDK-Installation hinzu. Zum Beispiel: alter Eintrag: SET PATH=.;c:\dos; neuer Eintrag: SET PATH=.;c:\dos; c:\Compiler\Java\jdk1.6.0\bin; Das Semikolon dient zur Trennung der einzelnen Verzeichnisangaben in PATH. Wenn Sie gar keine PATH-Angabe finden, dann fügen Sie eine neue PATH-Anweisung hinzu, z. B.: SET PATH=c:\Compiler\Java\jdk1.6.0\bin;
■
Windows ME/2000/XP: Die Umgebungsvariable PATH wird über den Dialog der Systemeigenschaften verwaltet. Der Weg dorthin ist lang und von Betriebssystem zu Betriebssystem verschieden. Unter Windows ME lautet er START/PROGRAMME/ZUBEHÖR/SYSTEMTOOLS/SYSTEMINFORMATIONEN, Menü TOOLS/SYSTEM KONFIGURATION, Register UMGEBUNG. Unter Windows 2000/XP/Vista rufen Sie über START oder START/EINdie Systemsteuerung auf, schalten diese gegebenenfalls in die klassische Ansicht und gelangen via SYSTEM, Register ERWEITERT (ERWEITERTE SYSTEMEINSTELLUNGEN unter Vista), Schalter UMGEBUNGSVARIABLEN zum Ziel. Danach können Sie die Systemvariable auswählen, zum Bearbeiten laden und den Pfad zu den Java-Programmen anhängen (siehe Abbildung 3.3). STELLUNGEN
86
Installation des JDK
Abbildung 3.3: Anpassung der PATH-Variablen unter Windows Vista (XP)
1 2
3 4 5 6 7 ■
Unix/Linux: Analoges Vorgehen: Suchen Sie die Pfadangabe path in der zuständigen ini-Datei (je nach Konfiguration .login, .profile, .tcshrc, .bashrc o. ä.) und fügen Sie das Java-Bin-Verzeichnis /home/ihrname/ Java/jdk1.6.0/bin in der nächsten Zeile nach der bisherigen Pfadangabe hinzu.
8 9
Für die C-Shell sieht dies beispielsweise wie folgt aus:
10
set path = (/home/ihrname/Java/jdk1.6.0/bin . $path)3
Für die Bourne-Again-Shell (bash) könnte der Eintrag so lauten:
11
export PATH=/home/ihrname/Java/jdk1.6.0/bin:.:${PATH}
oder
12
PATH="/home/ihrname/Java/jdk1.6.0/bin:.:$PATH"4
Um den neuen Pfad zu testen, müssen Sie den Rechner neu starten und die Konsole öffnen (Windows-Anwender rufen dazu je nach Betriebssystem START/PROGRAMME/MSDOS-EINGABEAUFFORDERUNG, START/PROGRAMME/EINGABEAUFFORDERUNG oder START/PROGRAMME/ZUBEHÖR/EINGABEAUFFORDERUNG auf). Tippen Sie hinter dem Prompt javac ein und schicken Sie den Befehl 3 4
13 14
Mehrere Verzeichnisangaben werden durch Leerzeichen getrennt. Mehrere Verzeichnisangaben werden durch einen Doppelpunkt getrennt.
87
Das erste Programm
durch Drücken der (Enter)-Taste ab. Statt einer Fehlermeldung, dass der Befehl nicht gefunden wurde, sollten die Aufrufoptionen des Java-Compilers erscheinen. Abbildung 3.4: Der Pfad wurde korrekt aktualisiert, die JavaTools können von jedem Verzeichnis aus aufgerufen werden
Setzen des Klassenpfads Die zweite Einstellung betrifft die CLASSPATH-Variable. Java-Programme bestehen aus Klassen. Diese sind in Quelltextdateien (Extension .java) oder als Bytecode in Class-Dateien (Extension .class) definiert. Wenn Sie ein Java-Programm kompilieren oder ausführen, suchen sich die zuständigen Java-Tools (Compiler oder Interpreter) zu allen im Programm verwendeten Klassen die zugehörigen Quelltext- oder Class-Dateien zusammen. Standardmäßig wird im aktuellen Arbeitsverzeichnis (also von dem aus z. B. der Interpreter aufgerufen worden ist) und in den Class-Archiven .jar im LIB-Verzeichnis der JDK-Installation gesucht (gilt erst ab JDK 1.2). Solange Sie für jedes Programm ein eigenes Verzeichnis anlegen und die Quelltextdateien des Programms zusammen in diesem Verzeichnis speichern, gestaltet sich die Programmerstellung und -ausführung daher recht einfach. Probleme gibt es meist nur dann, wenn irgendein anderes Programm bei der Installation die CLASSPATH-Variable gesetzt hat. Dann suchen die JDK-Tools nämlich nicht mehr im aktuellen Verzeichnis – es sei denn, das aktuelle Verzeichnis – symbolisiert durch einen Punkt (.) – wurde explizit in der Umgebungsvariablen mit aufgeführt. Meist ist dies aber nicht der Fall, sodass Sie den Eintrag für das aktuelle Verzeichnis selbst anhängen müssen. alter Eintrag: SET CLASSPATH=c:\interbase neuer Eintrag: SET CLASSPATH=c:\interbase;.
88
Welche Art Programm darf es sein?
■
Windows 95/98: Setzen oder erweitern Sie die CLASSPATH-Variable in der autoxec.bat-Datei: SET CLASSPATH = c:\interbase;.
■
■
Windows ME/2000/XP/Vista: Bearbeiten Sie die CLASSPATH-Umgebungsvariable im Dialogfeld der Systemeigenschaften (analog zu der oben beschriebenen Einrichtung der PATH-Variablen). Linux: Setzen oder passen Sie die CLASSPATH-Variable in .login, .profile, .tcshrc, .bashrc o. Ä. (analog zu der oben beschriebenen Einrichtung der PATH-Variablen). Für csh beispielsweise:
1 2
setenv CLASSPATH /bin/interbase;.
3 3.2.3
Wo Sie weitere Hilfe finden
Weitere Informationen finden Sie auf der Support-Site zu diesem Buch, www.carpelibrum.de, und auf den Download-Webseiten des Sun-Servers.
4
Leider mussten wir in der Vergangenheit auch feststellen, dass die großen Browser- und Betriebssystemhersteller ihre Produktzyklen nicht nach den Überarbeitungszyklen unserer Bücher ausrichten. Sollten Sie Probleme mit neuen Betriebssystemen oder Browserversionen haben, schauen Sie bitte auf unserer Website nach.
5 6
Sollten die Hinweise im Buch und auf der Website nicht ausreichen, haben Sie keine Scheu, sich per E-Mail an uns zu wenden (
[email protected]).
3.3
7 8
Welche Art Programm darf es sein?
In Java können Sie die verschiedensten Arten von Programmen schreiben. Die drei wichtigsten Grundtypen sind: ■ ■ ■
9
Konsolenanwendungen, GUI-Anwendungen und Applets.
10 11
Zum Einstieg in die Java-Programmierung konzentrieren wir uns ganz auf die Konsolenanwendungen. Der GUI-Programmierung ist Teil IV dieses Buches gewidmet, Applets werden Sie in Teil VI kennenlernen.
3.4
12
Konsolenanwendungen
13
Konsolenanwendungen sind – unserer Meinung nach zu Unrecht – die Stiefkinder der modernen Software-Entwicklung. Ohne Fenster und ohne grafische Benutzeroberfläche fristen sie in der bunten Welt der Window-Manager ein kümmerliches Nischendasein. Viele PC-Benutzer, insbesondere aus der Microsoft-Windows-Gemeinde, wissen nicht einmal mehr, was eine Konsole ist und wie man mit ihr arbeitet. Die Schuld daran trägt nicht allein der Zeit-
14
89
Das erste Programm
geist, sondern auch die Microsoft-Betriebssystemdesigner, die – nachdem sie zu einer Zeit, als Mac- und UNIX-Benutzer schon längstens von leistungsfähigen Window-Managern profitierten, ihre Anhänger mit mittelmäßigen Surrogaten wie Windows 3.1 oder Windows 95 quälten – nunmehr dazu übergegangen sind, die Erinnerung an MS-DOS zu löschen und die Konsole (unter Windows meist Eingabeaufforderung oder MS-DOS-Eingabeaufforderung tituliert) immer geschickter vor den Augen der Anwender zu verbergen. Dabei haben Konsolenanwendungen durchaus ihre Vorzüge: die Programme sind klein und schlank, schnell in der Ausführung und – sofern man einigermaßen mit der Arbeit auf der Konsole vertraut ist – gut zu bedienen. Sie eignen sich vorzüglich als Lehr- und Übungsbeispiele zum Einstieg in die Programmierung (weswegen es sich bei den Beispielprogrammen aus den ersten Teilen dieses Buches fast ausnahmslos um Konsolenanwendungen handelt), werden aber auch weiterhin in der professionellen SoftwareEntwicklung eingesetzt – beispielsweise für Webserver-Software (gelegentlich ergänzt um fensterbasierte Konfigurationsprogramme), Tools für Systemadministratoren oder eben für Entwickler (siehe Anhang zu JDK-Tools).
TIPP
Eine kurze Einführung in die Bedienung der Konsole finden Sie im Exkurs »Bildschirmausgaben und Konsolenanwendungen« in Kapitel 2.1. Ausführlichere Informationen finden Sie auf unserer Website www.carpelibrum.de.
3.4.1
Ein einfaches Konsolen-Grundgerüst
Viele Programmierlehrbücher beginnen mit einem kleinen Programm, das die Meldung »Hello World« auf den Bildschirm ausgibt. Wir wollen uns dieser Tradition anschließen und eine Konsolenanwendung erzeugen, die sich mit einem freudigen »Hallo Welt« meldet. Listing 3.1: HalloWelt.java – Ein einfaches Konsolen-Grundgerüst // Das erste Konsolenprogramm public class HalloWelt { public static void main(String[] args) { System.out.println("Hallo Welt!"); } }
Das Programm besteht aus einer einzigen Klasse namens HalloWelt, die wiederum über eine einzige Methode, main(), verfügt. main() ist die Eintrittsmethode, mit der die Programmausführung beginnt. Jedes Programm muss genau eine main()-Methode enthalten und diese muss als static deklariert sein, damit sie von der Java Virtual Machine ohne Instanziierung ihrer Klasse ausgeführt werden kann.
90
Konsolenanwendungen
System ist eine Klasse aus der Java-Standardbibliothek. Sie enthält ein statisches PrintStream-Objekt namens out, über dessen println()-Methode Strings auf den Konsolenbildschirm ausgegeben werden können.
Die main()-Methode muss immer mit dem Schlüsselwort public deklariert werden, damit sie von der Java Virtual Machine aufgerufen und ausgeführt werden kann. Die zugehörige Klasse muss nicht, wird aber meist ebenfalls als public deklariert. (Einfach weil es in Java sinnvoll ist, Klassen mit public-Elementen als public zu deklarieren.) Wenn Sie eine Klasse als public deklarieren, müssen Sie sie in einer Datei speichern, die den gleichen Namen hat wie die Klasse! (Woraus folgt, dass in einer Datei keine zwei public-Klassen definiert werden können.)
1
TIPP
2
3
Kommentare 4
Die erste Zeile im obigen Quelltext ist ein Kommentar, der der Erläuterung des Quelltextes dient und vom Compiler ignoriert wird. Java kennt drei Arten von Kommentaren: ■ ■ ■
5
//-Kommentare reichen bis zum Ende der aktuellen Zeile. Mehrzeilige Kommentare werden mit /* eingeleitet und enden mit */. Kommentare, die zwischen den Zeichenfolgen /** und */ stehen, können mithilfe des JDK-Tools javadoc zu einer HTML-Dokumentation im Stil der Java-API-Dokumentation aufbereitet werden (siehe Anhang zu JDK-Tools).
3.4.2
6 7 8
Konsolenanwendungen erstellen und ausführen
Um Konsolenanwendungen auf Ihrem Computer zu erstellen und auszuführen, gehen Sie folgendermaßen vor: 1.
9
Öffnen Sie einen Texteditor. Rufen Sie einen beliebigen Texteditor auf, mit dem Sie Ihren Quelltext als ASCII-Text abspeichern können.
10
Unter Windows eignet sich beispielsweise der Notepad-Editor, den Sie über das Start-Menü mit START/PROGRAMME/ZUBEHÖR/EDITOR aufrufen können. (Alternativ können Sie START/AUSFÜHREN aufrufen, in dem erscheinenden Dialogfeld notepad eingeben und abschicken. Unter Windows Vista geht es noch einfacher: Sie tippen notepad einfach in das Suchfeld des Startmenüs ein.)
11 12 13
Unter Linux können Sie den vi, KEdit oder KWrite verwenden. (Integrierte Entwicklungsumgebungen wie NetBeans, der JBuilder von Borland oder Eclipse von IBM verfügen über einen eingebauten Editor.) 2.
14
Geben Sie den Java-Quelltext ein. Legen Sie in Ihrem Editor eine neue Datei an, tippen Sie obigen Quelltext ein und speichern Sie die Datei unter dem Namen HalloWelt.java. 91
Das erste Programm
Wichtig ist dabei, dass die Quelltextdatei exakt den gleichen Namen trägt wie die in dem Quelltext definierte Hauptklasse (hier also class HalloWelt), wobei auch die Groß- und Kleinschreibung zu beachten ist. Weiterhin wichtig ist, dass die Datei die Dateiendung .java trägt! Bei Verwendung von Notepad gibt es manchmal Probleme, weil der Notepad-Editor die Dateiendung .txt anhängt (aus HalloWelt.java wird dann HalloWelt.java.txt). Um dies zu vermeiden, gibt es zwei Möglichkeiten. Die erste Lösung besteht darin, den kompletten Dateinamen, samt Extension, in Anführungszeichen zu setzen: »HalloWelt.java«. Die zweite Möglichkeit ist, die Extension .java im Windows Explorer zu registrieren. Speichern Sie dazu nach Methode 1 eine Datei mit der Extension .java. Wechseln Sie danach in den Windows Explorer und doppelklicken Sie auf die Datei. Ist die Extension noch nicht registriert, erscheint jetzt der ÖFFNEN MIT-Dialog. Wählen Sie als gewünschtes Bearbeitungsprogramm Notepad aus und aktivieren Sie die Option DIESE DATEI IMMER MIT DIESEM PROGRAMM ÖFFNEN. Wenn Sie den Dialog jetzt abschicken, wird die Extension .java registriert und mit Notepad als Standardverarbeitungsprogramm verknüpft. Danach können Sie .java-Dateien per Doppelklick in Notepad laden und werden nie wieder Ärger mit an Java-Dateien angehängte .txt-Extensionen haben. (NetBeans JBuilder, Eclipse und andere integrierte Entwicklungsumgebungen arbeiten meist mit Projekten. Lesen Sie im Anhang oder in der Dokumentation zu Ihrer Entwicklungsumgebung nach, wie Sie neue Projekte anlegen und Quelltexte eingeben.) 3. Kompilieren Sie den Quelltext. Falls Sie mit dem JDK und einem einfachen Texteditor arbeiten (und nicht mit einer Entwicklungsumgebung), müssen Sie ein Konsolenfenster öffnen, in diesem zum Verzeichnis Ihrer Java-Quelldatei wechseln und dort den Java-Compiler javac aufrufen: Prompt:> javac HalloWelt.java
Im Gegensatz zu Linux-Anwender sind viele Windows-Anwender heutzutage gar nicht mehr mit dem Umgang mit der Konsole vertraut. Unter Windows heißt die Konsole »Eingabeaufforderung« oder MSDOS-Eingabeaufforderung und wird über das Start-Menü aufgerufen (START/PROGRAMME/ oder START/PROGRAMME/ZUBEHÖR). In der Konsole müssen Sie nun mithilfe des cd-Befehls in das Verzeichnis wechseln, in dem Sie die Datei HalloWelt.java abgespeichert haben. Nehmen wir an, es war das Verzeichnis c:\Beispiele\Kapitel03. Dann tippen Sie hinter dem Prompt der Konsole den Befehl cd c:\Beispiele\Kapitel03 ein und schicken ihn durch Drücken der (Enter)-Taste ab.
92
Konsolenanwendungen
Nun kommen wir zum eigentlichen Kompilieren, bei dem der JavaQuelltext (die Datei mit der Extension .java) an den Java-Compiler (javac) übergeben wird. Tippen Sie zum Kompilieren folgenden Befehl ein: javac HalloWelt.java
Schicken Sie den Befehl wiederum durch Drücken der (Enter)-Taste ab. Dieser Aufruf erzeugt eine ausführbare Bytecode-Datei mit der Extension .class – in unserem Beispiel also HalloWelt.class.
1 2
Sollten Sie beim Abschicken des Befehls eine Meldung in der Form »Befehl oder Dateiname nicht gefunden« erhalten, ist Ihr System nicht so eingerichtet, dass Sie die Java-Entwicklungsprogramme aus jedem beliebigen Verzeichnis aufrufen können. Lesen Sie dann bitte noch einmal den Abschnitt »Erweiterung des Systempfads« in Kapitel 3.2.
3 4
(In NetBeans, JBuilder, Eclipse und anderen integrierten Entwicklungsumgebungen rufen Sie den entsprechenden Menübefehl zur Kompilation Ihres Projekts auf.)
5
4. Lassen Sie die fertige Anwendung ausführen. Dazu rufen Sie den Java-Interpreter (java) auf und übergeben diesem als Parameter den Namen Ihrer kompilierten Bytecode-Datei, aber ohne die Endung .class:
6 7
Prompt:> java HalloWelt
Sollten Sie daraufhin eine Fehlermeldung der Form »Exception in thread "main" java.lang.NoClassDefFoundError: HalloWelt« erhalten, bedeutet dies, dass der Interpreter die gewünschte Java-Klasse nicht findet. Dies kann daran liegen, dass die .class-Datei nicht erzeugt wurde (kontrollieren Sie nach dem Kompilieren mithilfe des DOSBefehls dir, ob die Datei HalloWelt.class im Verzeichnis angelegt wurde). Möglich ist auch, dass Sie den Klassennamen nicht exakt so eingegeben haben, wie er im Quelltext definiert ist (auf gleiche Großund Kleinschreibung achten). Meist liegt es aber daran, dass irgendeines der auf Ihrem System installierten Programme die Java-Umgebungsvariable CLASSPATH so gesetzt hat, dass die Class-Dateien im aktuellen Verzeichnis nicht mehr gefunden werden. Dann müssen Sie die CLASSPATH-Variable bearbeiten und um den Platzhalter für das aktuelle Verzeichnis (;.) erweitern. Wie dies geht, ist unter »Anpassen des Systems« beschrieben.
8 9 10 11 12 13
(In JBuilder, Eclipse und anderen integrierten Entwicklungsumgebungen rufen Sie den entsprechenden Befehl zur Ausführung Ihres Projekts auf.)
14
93
Das erste Programm
Abbildung 3.5: Kompilation und Ausführung der Anwendung HalloWelt im Verzeichnis C:\Beispiele\Kapitel03
3.5
Ein- und Ausgabe für Konsolenanwendungen
Die Java-Bibliothek enthält Hunderte von Klassen, die Sie bei den verschiedensten Programmieraufgaben – von der Abfrage des Datums bis zur Webservice-Programmierung – unterstützen. Die Java-Bibliothek wird ständig erweitert und verbessert. Und manchmal lösen auch neuere Klassen veraltete Implementierungen ab. Auf keinem Gebiet wurde allerdings mehr experimentiert, nachgebessert und hinzugefügt als bei der Ein- und Ausgabe für die Konsole. Unser Favorit für die Ein- und Ausgabe ist die in Java 6 neu hinzugekommene Console-Klasse. Nicht weil sie die neueste Klasse ist oder weil sie in allen Belangen besser wäre als die alten Ein- und Ausgabe-Klassen, sondern weil sie die deutschen Umlaute unterstützt. Daneben bedienen wir uns aber auch gelegentlich der älteren Ein- und Ausgabe-Klassen, die zum Teil gleichberechtigt neben Console stehen, zum Teil an sehr traditionsreiche Techniken gekoppelt sind, die ein guter Java-Programmierer kennen sollte. Die folgenden Abschnitte zeigen Ihnen, wie Sie Daten über die Konsole ausgeben oder einlesen können, und verschaffen Ihnen einen Überblick über die zur Verfügung stehenden Techniken. Detailliertere Informationen zu den einzelnen Klassen und Techniken finden Sie in Kapitel 31.
3.5.1
Ausgabe
Der klassische Weg, in Java auf die Konsole zu schreiben, führt über das outObjekt der Bibliotheksklasse System und deren println()-Methode.
94
Ein- und Ausgabe für Konsolenanwendungen
System.out.println() Das out-Objekt repräsentiert im Java-Quelltext den Konsolenbildschirm. Um Strings (Text) auszugeben, rufen Sie einfach die Methode println() des Objekts auf: System.out.println("Hallo Welt!");
1
Mit der gleichen Methode können Sie auch Werte elementarer numerischer Datentypen ausgeben. Die Werte werden automatisch in ihre String-Darstellung umgewandelt:
2
double zahl = 0.234; System.out.println(zahl); // gibt den Text "0.234" auf die Konsole aus
Ja, dank des +-Operators können Sie sogar die Ausgabe von Strings mit der Ausgabe von Variablenwerten koppeln:
3
System.out.println("Der Wert der Variablen zahl lautet: " + zahl);
4
Die Methode println() schließt die Ausgabe immer mit einem Zeilenumbruch ab. Sollte dies nicht gewünscht sein, verwenden Sie einfach statt println() die Methode print():
5
System.out.print(zahl);
6
System.out.printf() Leider gibt die println()-Methode dem Programmierer keine Gelegenheit, in die Formatierung der Ausgabe einzugreifen. Aus diesem Grunde wurde in Java 5 die Methode printf() eingeführt, mit der Werte und Variableninhalte über Platzhalter in einen Ausgabestring eingebettet und formatiert werden können. (Umsteigern von C/C++ dürfte diese Form der Ausgabe wohlbekannt sein.)
7 8 9
System.out.printf("Der Wert von 'zahl' ist: %1$f \n", zahl);
Ausgabe: 10
Der Wert von 'zahl' ist: 0,234000
Der Platzhalter in diesem Beispiel lautet %1$f. Die Platzhalter scheinen recht kryptisch, sind letzten Endes aber ganz einfach zu lesen. Jeder Platzhalter beginnt mit %. Dann folgt eine optionale Angabe n$, die besagt, dass der Platzhalter durch den Wert des n-ten Arguments aus der nachfolgenden Argumentenliste ersetzt werden soll. Wenn Sie die n$-Angabe weglassen, wird dem ersten Platzhalter automatisch das erste Argument zugewiesen, dem zweiten Platzhalter das zweite Argument und so weiter.
11 12 13
// Zwei Argumente System.out.printf(" %1$f %2$f %n", eineZahl, nochEineZahl); System.out.printf(" %f %f %n", eineZahl, nochEineZahl); // Ein Argument zwei Mal ausgeben System.out.printf("%1$f %1$f %n", zahl);
14
Das Suffix f ist eine Typbezeichnung, die angibt, dass eine Gleitkommazahl ausgegeben werden soll. Andere Suffixe sind s für Strings oder d für Integer. 95
Das erste Programm
Die printf()-Methode schließt die Ausgabe nicht mit Zeilenumbruch ab. Sie können in den auszugebenden String aber den vordefinierten Platzhalter %n einbauen, der für einen Zeilenumbruch steht. (Alternativ können Sie auch die Escape-Sequenz \n einbauen.) Ein großer Vorzug von printf() ist, dass die auszugebenden Variableninhalte über die Platzhalter formatiert werden können. Beispielsweise können Sie festlegen, dass für Gleitkommazahlen nur maximal zwei Nachkommastellen ausgegeben werden sollen. Außerdem wird zur Abtrennung der Nachkommastellen das Komma (statt des Punktes) verwendet. System.out.printf("Der Wert von 'zahl' ist: %1$.2f %n", zahl);
Ausgabe: Der Wert von 'zahl' ist: 0,23
Ausführliche Erläuterungen zu printf() finden Sie in Kapitel 31.3, Abschnitt »Formatter und die Formatierungsstrings«. REF
Console und printf() Leider kann man über das System.out-Objekt keine Umlaute auf die Konsole ausgeben – weder deutsche Umlaute wie ä, ö oder ß noch Umlaute anderer Sprachen wie œ, å oder ç. Versuchen Sie es dennoch, erscheinen auf der Konsole irgendwelche falschen Zeichen oder Symbole. Der Grund dafür ist die – etwas veraltete – OEM-Zeichencodierung der Windows-Konsole, die von dem System.out-Objekt nur halbherzig unterstützt wird.5 Neu in Java 6
Doch glücklicherweise gibt es seit Java 6 nun eine Lösung für die einfache Ausgabe auf Konsole, inklusive Formatierung und Unterstützung für die Umlaute: die Klasse Console aus dem Paket java.io und ihre printf()Methode. Allerdings gibt es kein vordefiniertes Objekt System.console welches analog zu System.out das Konsolenfenster repräsentiert. Dies ist aber nicht weiter tragisch, denn es gibt eine Methode System.console(), die Ihnen eine Referenz auf ein solches Objekt zurückliefert. Die printf()-Methode von Console verwenden Sie dann genauso wie die printf()-Methode von System.out. import java.io.*;6 ... Console cons = System.console(); cons.printf("Grüße"); cons.printf("\n");
5
6
96
// Console-Objekt beschaffen // Text ausgeben // Text ausgeben
Alle Zeichen aus dem 7-Bit-ASCII-Zeichensatz (dies wären die Buchstaben von A bis Z, die Ziffern und die wichtigsten Satzzeichen) werden korrekt ausgegeben. Zusätzliche Zeichen des erweiterten 8-BitOEM-Zeichensatzes können nur mit Tricks und Wissen über die beteiligten Zeichencodes korrekt ausgegeben werden. Die import-Anweisung wird gleich im Anschluss in Abschnitt 3.6 erklärt.
Ein- und Ausgabe für Konsolenanwendungen
Wenn Sie nur wenige, über den Quelltext verstreute Ausgaben vornehmen, können Sie den printf()-Aufruf auch direkt an den console()-Aufruf anhängen: System.console().printf("Grüße");
Ausführlichere Informationen zur Klasse Console finden Sie in Kapitel 31.4.
1 REF
Unsere Empfehlung
2
Verwenden Sie ■
System.console().printf() für alle Ausgaben, die Teil des endgültigen Programms sein sollen.
3
(Ihre Programme profitieren dann von korrekt dargestellten Umlauten, länderspezifischen Gleitkommaformatierungen (auf deutschen Systemen beispielsweise Komma zur Abtrennung der Nachkommastellen) und weiteren von Ihnen gesteuerten Formatierungen wie z. B. die Anzahl der Nachkommastellen.) ■
4 5
System.out.println() für Kontrollausgaben, um sich während eines Testlaufs schnell einmal über bestimmte Abläufe im Programm oder Inhalte einzelner Variablen zu informieren, sowie
6
als Alternative zum Ausgeben von Leerzeilen (Zeilenumbrüchen). Wir selbst werden es in diesem Buch so halten, dass wir in Listings mit Programmcharakter (wobei der praktische Nutzen der Programme dahingestellt sei) mit dem Console-Objekt arbeiten und in Listings, die rein didaktischen oder veranschaulichenden Zwecken dienen, uns der guten alten System.out.println()-Ausgaben bedienen.
3.5.2
7 8
TIPP
9
Eingabe
10
Während die Ausgabe auf die Konsole noch recht einfach ist, gestaltet sich das Einlesen von Werten über die Tastatur schon etwas schwieriger. Zwar gibt es als Pendant zu System.out auch ein Objekt System.in, das die Tastatur repräsentiert, doch verfügt dieses nur über Methoden, die Daten als unformatierte Byteströme einlesen. Um einzelne Werte (Strings, Integeroder Gleitkommazahlen) formatiert einzulesen, muss man einen anderen Weg gehen. Console und readLine() Mit der readLine()-Methode der Klasse Console können Sie eine ganze Eingabezeile von der Konsole einlesen. In der Praxis sieht dies meist so aus, dass Sie zuerst einen Text ausgeben, der den Benutzer auffordert, die gewünschten Daten einzugeben. Anschließend rufen Sie readLine() auf. Die Methode wartet, bis der Benutzer seine Daten eingegeben und die
11 12 13 Neu in Java 6
14
97
Das erste Programm
(Enter)-Taste gedrückt hat. Dann liest die Methode die komplette Eingabezeile ein und gibt sie als String zurück. import java.io.*; ... cons.printf("Geben Sie Ihren vollständigen Namen ein: "); String name = cons.readLine();
Wenn Sie den Benutzer auffordern, Zahlen einzugeben, müssen Sie bedenken, dass diese von der readLine()-Methode ebenfalls als Strings (beispielsweise als die Ziffernfolge "123") entgegengenommen und zurückgeliefert werden. Diese Strings müssen Sie dann erst noch in echte Zahlen umwandeln. Dafür gibt es die parse-Methoden der Klassen Integer, Long, Float und Double. Tabelle 3.1: Ausgesuchte Umwandlungsmethoden
Umwandlung in
Methode
int
Integer.parseInt(str);
long
Long.parseLong(str);
float
Float.parseFloat(str);
double
Double.parseDouble(str);
import java.io.*; ... cons.printf("Geben Sie Ihr Alter ein: "); String eingabe = cons.readLine(); int geburtsjahr = Integer.parseInt(eingabe);
TIPP
Wenn die Eingabe des Benutzers nicht in den gewünschten Zahlentyp umgewandelt werden kann, löst die betreffende parse-Methode eine Exception aus. Wie Sie diese Exceptions abfangen und behandeln können, erfahren Sie in Kapitel 16. Im Moment wollen wir den Quelltext nicht unnötig komplizieren und vertrauen darauf, dass der Benutzer Zahlen als Ziffernfolgen ("11" nicht "elf") und Nachkommastellen mit Punkt statt mit Komma abtrennt ("12.5"). Listing 3.2: Eingabe.java – Ein- und Ausgabe in Konsolenanwendungen import java.io.*; public class Eingabe { public static void main(String[] args) { String name; int geburtsjahr; // Objekt für Konsole beschaffen
98
Ein- und Ausgabe für Konsolenanwendungen
Console cons = System.console(); // Daten ausgeben und einlesen cons.printf("\n"); cons.printf(" Geben Sie Ihren vollständigen Namen ein: "); name = cons.readLine();
1
cons.printf(" Geben Sie Ihr Geburtsjahr ein: "); String eingabe = cons.readLine(); geburtsjahr = Integer.parseInt(eingabe); cons.printf("\n");
2
cons.printf(" %1$s, %2$d war ein sehr guter Jahrgang!", name, geburtsjahr); cons.printf("\n"); }
3 4
}
Aufruf: 5
C:\Beispiele\Kapitel03>java Eingabe Geben Sie Ihren vollständigen Namen ein: Sean Nase Geben Sie Ihr Geburtsjahr ein: 1965
6
Sean Nase, 1965 war ein sehr guter Jahrgang!
BufferedReader
7
Früher war das Einlesen von Werten über die Tastatur noch komplizierter, denn man musste sich zu System.in erst ein passendes Reader-Objekt basteln:
8
Listing 3.3: Eingabe_BufferedReader.java – Ein- und Ausgabe in Konsolenanwendungen
9
01 import java.io.*; 02 03 public class Eingabe_BufferedReader { 04 05 public static void main(String[] args) throws IOException { 06 int zahl; 07 08 System.out.println(); 09 System.out.print(" Geben Sie einen Integer-Wert ein: "); 10 BufferedReader tastatur = new BufferedReader(new InputStreamReader(System.in)); 11 String eingabe = tastatur.readLine(); 12 13 zahl = Integer.parseInt(eingabe); 14 System.out.println(" " + zahl); 15 System.out.println(); 16 } 17 }
10 11 12 13 14
99
Das erste Programm
Um die komplette Eingabe (bis zum (Enter)) auf einmal einzulesen, erzeugt das obige Programm in Zeile 10 zuerst für das Standardeingabegerät (System.in) ein InputStreamReader-Objekt: new InputStreamReader(System.in)
und auf der Basis dieses Objekts ein BufferedReader-Objekt: BufferedReader tastatur = new BufferedReader(...);
Dieses Objekt besitzt endlich eine Methode, mit der die Eingabezeile eingelesen werden kann: readLine(). In Zeile 11 wird die Eingabe eingelesen und in dem String eingabe abgelegt. Danach wird der String in einen int-Wert umgewandelt (vgl. vorangehender Abschnitt zu Console).
TIPP
Die zum Einlesen benutzte BufferedReader-Methode löst für unerwartet fehlschlagende Einlesevorgänge Exceptions aus, die unbedingt behandelt werden müssen. Um uns vor der ordnungsgemäßen Fehlerbehandlung zu drücken – wir werden uns erst in Kapitel 16 intensiver mit der Fehlerbehandlung durch Exceptions beschäftigen –, deklarieren wir die Methode main() einfach so, dass sie IO-Exceptions weiterleitet (Zeile 5). Scanner Listing 3.4: Eingabe_Scanner.java – Ein- und Ausgabe in Konsolenanwendungen 01 import java.util.Scanner; 02 03 public class Eingabe_Scanner { 04 05 public static void main(String[] args) { 06 07 System.out.println(); 08 System.out.print(" Geben Sie einen Integer-Wert ein: "); 09 10 Scanner sc = new Scanner(System.in); 11 int zahl = sc.nextInt(); 12 13 System.out.println(" Sie haben " + zahl + " eingegeben"); 14 System.out.println(); 15 } 16 }
Um das Einlesen von Benutzereingaben zu vereinfachen, wurde in Java 5 die Klasse Scanner eingeführt, die im Paket java.util der Standardbibliothek definiert ist. Im obigen Beispiel wird in Zeile 10 ein Objekt von Scanner erzeugt. Damit der Scanner von der Tastatur liest, wird als Argument System.in übergeben. Der Rest ist dank der next-Methoden der Klasse Scanner recht einfach.
100
Klassen, Pakete und die Standardbibliothek
Methode
Beschreibung
String next()
Liest eine Eingabe als String ein. (Liest nur bis zum ersten Leerzeichen.)
int nextInt()
Liest eine Eingabe als Integer ein. (Liest nur bis zum ersten Leerzeichen.)
int nextDouble()
Liest eine Eingabe als Double ein. (Liest nur bis zum ersten Leerzeichen.)
String nextLine()
Liest eine Eingabe als String ein.
Tabelle 3.2: Ausgesuchte next-Methoden der Klasse Scanner
1 2
Der Aufruf einer next-Methode hält das Programm so lange an, bis der Benutzer eine Eingabe durch Drücken der (Enter)-Taste abschickt. Dann liest die next-Methode die Eingabe ein und liefert sie als Rückgabewert an das Programm. (Mehr zur Klasse Scanner in Kapitel 31.3).
3 4
Beachten Sie, dass das obige Programm nur unzureichend gegen inkorrekte Benutzereingaben abgesichert ist. Gibt der Benutzer falsch formatierte Werte ein, die sich nicht in einen int-Wert umwandeln lassen (»3.24«, »drei«), löst das Programm eine Exception aus und wird beendet.
5
TIPP
6
Unsere Empfehlung Verwenden Sie ■
■
■
7
System.console().readLine() zum Einlesen von Strings ( vor allem wenn diese Umlaute enthalten könnten)
und
8
in Kombination mit den parse-Methoden oder Scanner7 auch zum Einlesen von Zahlenwerten und anderen Datentypen.
9
BufferedReader, wenn Sie für Ihre Einleseroutinen gezielt die Flexibilität und Anpassungsfähigkeit des Stream-Modells nutzen wollen (siehe Kapitel 31.2). Scanner, in Kombination mit System.in oder System.console().reader(), um komplexere Eingaben schnell und bequem analysieren, parsen und formatieren zu können (siehe Kapitel Scanner).
10 11 12
3.6
Klassen, Pakete und die Standardbibliothek
13
Ebenso wie C oder C++ gibt es auch in Java keine in die Sprache integrierten Befehle für typische Programmieraufgaben, wie z. B. die Berechnung eines Sinus, das Vergleichen von Strings oder auch nur das Einlesen von Daten über die Tastatur bzw. die Ausgabe von Strings auf die Konsole. 7
14
Scanner sc = new Scanner(cons.reader());
101
Das erste Programm
Zum Ausgleich wird Java mit einer umfangreichen Standardbibliothek ausgeliefert, die für nahezu jede grundlegende Programmieraufgabe (Abfragen der Systemzeit, Stringmanipulationen, Sinusberechnung, Ein- und Ausgabe und, und, und) eine Lösung bereithält – allerdings stets in Form einer Klasse. Manche dieser Klassen deklarieren statische Elemente, auf die Sie direkt über den Klassennamen zugreifen können – so z. B. die Klasse Math: double hoehe = entfernung * Math.tan(Math.toRadians(winkel));
Andere Klassen instanziieren Sie, um Objekte der Klasse zu erzeugen – beispielsweise die Klasse String: String gruss = new String("Hallo ");
Danach können Sie die Objekte mithilfe der public-Elemente der Klassen bearbeiten: String name = new String("Fred"); gruss.concat(name); // hängt name an gruss an
Nicht selten enthalten die Klassen überladene Methoden (und Konstruktoren), die eine Operation je nach übergebenen Argumenten unterschiedlich ausführen. Wenn Sie beispielsweise die statische Methode println() mit einem String-Argument aufrufen, gibt die Methode den String auf die Konsole aus und bricht danach die Zeile um. Wenn Sie die Methode ohne Argument aufrufen, gibt die überladene Methode nur einen Zeilenumbruch aus: System.out.println("Text ausgeben"); System.out.println();
Pakete
// Text und Zeilenumbruch // Zeilenumbruch
Die Java-Standardbibliothek ist in sogenannte Pakete organisiert. Diese ordnen die Klassen nicht nur nach ihrer Funktionalität, sondern helfen auch, Namenskonflikte (in einem Programm darf es keine zwei Klassen mit gleich lautenden Namen geben!) zu vermeiden. Grundsätzlich gilt: Wenn Sie auf eine Klasse zugreifen wollen, müssen Sie den Namen des Pakets, in dem die Klasse definiert ist, voranstellen. Ist das Paket selbst Teil eines übergeordneten Pakets, müssen Sie den Pfad vom obersten Paket bis zum Paket der Klasse angeben. Die Klasse Scanner ist beispielsweise im Paket util definiert, das selbst wieder Teil des Pakets java ist. Der korrekte Zugriff auf den Konstruktor der Klasse lautet daher: java.util.Scanner( );
Wenn Ihnen die Voranstellung des Paketpfads zu lästig ist, können Sie die Klassennamen eines Pakets zu Anfang des Quelltextes einmalig importieren. Danach können Sie die Klassennamen direkt verwenden:
102
import java.util.Scanner;
// importiert den Klassennamen Scanner
import java.util.*;
// importiert alle KLassennamen aus // java.util, die im Quelltext verwendet // werden.
Klassen, Pakete und die Standardbibliothek
Lediglich die Namen der Klassen im Paket java.lang – hierzu gehören unter anderem System, Integer und Math – müssen nicht explizit angegeben oder importiert werden. Beachten Sie, dass in Java die allgemein akzeptierte Konvention gilt, dass Paketnamen (meist) mit Kleinbuchstaben geschrieben werden, während Klassennamen mit einem Großbuchstaben beginnen. Felder und Methoden beginnen mit Kleinbuchstaben. In Namen, die aus mehreren Wörtern zusammengesetzt sind, beginnen die nachfolgenden Wörter mit Großbuchstaben.
1
TIPP
2
Mehr zum Konzept der Pakete in Kapitel 10.1.
3
REF
4 5 6 7 8 9 10 11 12 13 14
103
Inhalt
4
Java-Stilkonventionen 1 2
Die Java-Syntax gibt vor, wie Deklarationen, Definitionen und Anweisungen aufgebaut sein müssen, damit sie vom Java-Compiler akzeptiert werden. Verstöße gegen diese Syntaxregeln werden mit Fehlermeldungen bestraft. So pedantisch der Compiler auf die Einhaltung der syntaktischen Formen achtet, so freizügig ist er, wenn es um Fragen des Stils geht: Sollen Variablennamen mit Groß- oder mit Kleinbuchstaben beginnen, sollen Variablennamen von Klassennamen durch die Schreibweise unterschieden werden und soll man Unterstriche in zusammengesetzten Variablennamen verwenden? Sollen Anweisungsblöcke eingerückt werden und, wenn ja, wie weit? Wo sollen die Klammern des Anweisungsblocks stehen? Sollen Operatoren und Operanden durch Leerzeichen getrennt werden und wie sollen Kommentare aussehen? Zu viel Freiheit kann manchmal recht verwirrend sein. Aus diesem Grund möchten wir Ihnen mit den folgenden Konventionen, die teils auf Suns Empfehlungen zurückgehen, teils am Stil der Java-Bibliotheken orientiert sind, ein wenig Halt geben. Der eine oder andere Leser, der schon einmal mit Visual C++ gearbeitet hat oder ein Lehrbuch gelesen hat, das von einem Microsoft-Entwickler geschrieben wurde, wird sich vielleicht fragen, warum wir die unter MicrosoftEntwicklern so populäre Ungarische Notation nicht unterstützen. Die Ungarische Notation wurde für die Windows-Programmierung mit C entwickelt, zu einer Zeit als Daten noch nicht in Objekten organisiert werden konnten und die Windows-Programmierer zudem mit den vielen zusätzlichen Typ-Aliasen der Windows-API zu kämpfen hatten. Kurzum, es gibt keinen Grund die Notation in objektorientiertem Code, noch weniger in Java-Code zu verwenden. (In Visual C++ wird sie aus Tradition weiter verwendet, in C# wurde sie aufgegeben.)
3
4 5 6 7 8 TIPP
9 10 11 12
Bezeichner Bezeichner, i. e. Namen für Klassen, Variablen, Konstanten etc., können aus einer beliebigen Kombination von Buchstaben, Ziffern und Unterstrichen bestehen, dürfen aber nicht mit einer Ziffer anfangen und dürfen weder einem Schlüsselwort noch einem der vordefinierten Literale true, false oder null entsprechen.
13
105
Index
14
Java-Stilkonventionen
Ansonsten haben Sie jegliche Freiheiten. Nutzen Sie diese, um Ihren Klassen, Variablen und anderen Elementen sinnvolle, informative und möglichst prägnante Namen zu geben. Und wenn Sie darüber hinaus noch die folgenden Konventionen zur Unterscheidung der verschiedenen Bezeichner beherzigen, haben Sie bereits viel für die Lesbarkeit Ihrer Quelltexte getan. Tabelle 4.1: Konventionen für Bezeichner
Bezeichner für
Schreibweise
Beispiel
Pakete
Pakete für den internen Gebrauch sollten mit Kleinbuchstaben statistik.tests beginnen. Der erste Teil sollte nicht »java« sein. Pakete, die wei- com.firma.stats tergegeben werden, sollten eindeutig sein. Sie können dies sicherstellen, indem Sie den Domänennamen Ihrer Firma oder Website (soweit vorhanden) komponentenweise umdrehen.
Klassen und Schnittstellen
Substantive; jedes Wort beginnt mit Großbuchstaben.
Vektor EineKlasse
Methoden
Verben; der Name beginnt mit Kleinbuchstaben, jedes weitere Wort mit Großbuchstaben.
wecken() alarmAusloesen()
Methoden, die die Werte von Feldern abfragen oder ändern, beginnen mit »get« oder »set«, gefolgt von dem Variablennamen.
getFeldname() setFeldname()
Methoden, die die Länge von etwas zurückliefern, heißen »length«.
length()
Methoden, die boolesche Variablen abfragen, beginnen mit »is«, gefolgt von dem Variablennamen.
isFeldname()
Methoden, die ihr Objekt umformatieren, beginnen mit »to«, gefolgt von dem Zielformat.
toString()
Felder
Substantive; der Name beginnt mit Kleinbuchstaben, jedes weitere Wort mit Großbuchstaben.
farbe einFeld
Konstanten
Vollständig in Großbuchstaben, einzelne Wörter werden durch Unterstriche getrennt.
PI MAX_ELEMENTE
Lokale Variablen und Parameter
Meist kurze, zum Teil auch symbolische Namen in Kleinbuchstaben.
l tmp
Einbuchstabige Namen werden üblicherweise so gewählt, dass der Buchstabe auf den Typ der Variablen, hinweist: l für long, i, j, k für int, e für Exception.
Blöcke Die öffnende geschweifte Klammer steht am Ende der einleitenden Zeile, dann folgen die eingerückten Anweisungen und zum Schluss die schließende Klammer, die selbst nicht eingerückt wird.
106
Java-Stilkonventionen
Tabelle 4.2: Blöcke
Block
Beispiel
Klassendefinition
class EineKlasse { private int feld = 0;
Methodendefinition
public int kurzeMethode() { return feld1; } public int laengereMethode() { // Anweisungen }
1 2
} if-Bedingung
switch-Verzweigung
if (i < 10) { // Anweisungen } else { // Anweisungen }
4
switch (i) { case 0:
5
case 1:
3
// Anweisungen break; // Anweisungen break;
6
} Schleifen
for (int k = 0; k < n; ++k) { // Anweisungen }
try-catch-Block
try { // Anweisungen } catch (Exception e) { // Fehlerbehandlung }
7 8 9 10
Kommentare Kommentieren Sie Klassen, Felder, Methoden mit /* ... */-Kommentaren vor den Elementen. Wenn Sie beabsichtigen, Ihren Code später richtig zu dokumentieren, überlegen Sie sich, ob Sie diese Kommentare nicht gleich von Anfang an JavaDoc-kompatibel formulieren (siehe Anhang zu den Java-Tools).
11 12
Wenn Sie einzelne Codeabschnitte in einer Methode kommentieren – etwa die Bedeutung einer Schleife oder der verschiedenen Zweige einer if-Verzweigungen –, verwenden Sie möglichst Einzeilen-Kommentare (//). Dies hält Ihnen die Option offen, beim Debuggen Ihrer Anwendung größere Codeabschnitte zwischenzeitlich durch mehrzeilige Kommentare auszukommentieren.
13 14
107
Java-Stilkonventionen
Anweisungen /* auskommentierte Anweisungen */ Anweisungen
// mit einzeiligen Kommentaren
Sonstiges Zwischen Operatoren und Operanden steht ein Leerzeichen.
108
Teil 2 Java-Grundlagen 111
Daten und Datentypen
5
149
Operatoren und Ausdrücke
6
181
Kontrollstrukturen
7
217
Klassen und Objekte
8
271
Arrays und Aufzählungen (enum)
9
299
Pakete, Gültigkeitsbereiche und andere Fragen
10
323
Stöbern und Entspannen
11
Inhalt
5
Daten und Datentypen 1 2
»Erklär’ mir Dein Datenmodell und ich sage Dir, was für eine Art von Programmiersprache Du bist.«
3
5.1
Das Java-Datenmodell 4
Seit sich Lady Ada Lovelace1 in der ersten Hälfte des 19. Jahrhunderts mit der Programmierung von Rechenmaschinen beschäftigte und dabei den Grundstein für die heutigen Programmiersprachen legte, wurden schätzungsweise an die 1500 Programmiersprachen entwickelt. Nahezu sämtliche dieser Sprachen – seien sie maschinenorientiert, objektorientiert, ereignisorientiert oder wie auch immer man sie beschreiben und kategorisieren möchte – sind vor allem eines: datenorientiert.
5 6
Nicht nur, dass die verschiedenen Programmiersprachen die Verarbeitung von Daten unterstützen, meist bilden die Daten das Herz der Programmiersprache und die mit der Datenverarbeitung verbundenen Konzepte ziehen sich wie ein Adergeflecht durch sämtliche Bereiche der Programmierung.
7
1
9
8
Lady Ada Lovelace geb. Byron (1815-1852) war die einzige eheliche Tochter des englischen Schriftstellers und Romantikers Lord Byron. Früh dem Einfluss des Vaters entzogen – Lord Byron verstieß Mutter und Kind, kaum dass die Mutter das Kindbett verlassen hatte –, wurde aus dem hochintelligenten Mädchen eine leidenschaftliche und selbstbewusste Frau, die vor allem eine Liebe hatte: die Mathematik. Ada betrieb mathematische Studien, konstruierte mechanische Maschinen und unterhielt eine rege Korrespondenz mit Charles Babbage. Wie viele seiner Zeitgenossen war der Philosoph, Nationalökonom und Mathematiker Babbage ein sehr vielseitig gebildeter und interessierter Mann. Er entwickelte die Theorie des Lebensversicherungswesens, dozierte über die Arbeitsteilung bei der industriellen Fertigung, beschäftigte sich mit Taucherglocken und Möglichkeiten der submarinen Navigation und konstruierte mechanische Rechenmaschinen. Sein Meisterstück, die »Analytische Maschine«, war die erste programmgesteuerte Rechenmaschine. An der Herleitung der mathematischen Grundprinzipien, nach denen die »Analytische Maschine« arbeitete, war auch Ada Lovelace maßgeblich beteiligt. Fasziniert und von Babbage ermutigt, beschäftigte sie sich intensiv mit der Rechenmaschine. Die »Programme« der Analytischen Maschine bestanden aus Lochkarten, Anweisungen und Daten wurden durch Lochmuster codiert. Lady Lovelace schrieb nicht nur mehrere solcher Programme, sie entwickelte auch eine eigene Notation, die ihr beim Entwurf der Programme half. Die fertigen Programme übersetzte sie danach in Lochmuster. Es sollte ihr jedoch nicht vergönnt sein, ihre Programme auf der Analytischen Maschine auszuführen und zu testen, denn Babbages »Analytische Maschine« wurde aufgrund technischer Schwierigkeiten und pekuniärer Engpässe niemals gebaut. Trotzdem gilt Babbage heute zu Recht als der »Vater des Computers« und Lady Ada Lovelace als »erste Programmiererin«. (1906 ließ Charles Babbages Sohn Henry das Rechenwerk der Maschine, »the mill«, fertig stellen, um zu beweisen, dass die Maschine funktionsfähig gewesen wäre.) Lady Ada Lovelace wurde nur 36 Jahre alt. Ihre »Programmiersprache« starb, wenn nicht mit ihr, so doch spätestens mit der Analytischen Maschine. Die von ihr entwickelten Konzepte und Ideen aber leben weiter und finden sich in vielen modernen Programmiersprachen wieder. Und seit den Achtzigern gibt es sogar eine Programmiersprache, die ihren Namen trägt: »Ada«, eine allgemeine, höhere Programmiersprache, die 1975 vom amerikanischen Verteidigungsministerium in Auftrag gegeben wurde.
10 11 12 13
111
Index
14
Daten und Datentypen
Kein Wunder also, dass die ersten Gehversuche mit einer neuen Programmiersprache gewöhnlich darin bestehen, einfache Daten zu repräsentieren, zu manipulieren und auszugeben (siehe beispielsweise das HalloWelt-Programm aus Kapitel 3.4). Keine Frage, dass hier, im Datenmodell, der Schlüssel zum tieferen Verständnis einer Programmiersprache liegt. Und kein Zweifel, dass dieses Verständnis nicht selten den Unterschied zwischen einem guten und einem besseren Programmierer ausmacht.
REF
Leser, für die dies die erste Begegnung mit einer höheren Programmiersprache ist, sollten zuerst Kapitel 2 lesen, um sich mit grundlegenden Konzepten wie Variablen, Anweisungen, Klassen etc. vertraut zu machen.
5.1.1
Repräsentation von Daten
Um Daten verarbeiten zu können, müssen sie erst einmal im Programmquelltext repräsentiert werden. In Java gibt es hierfür verschiedene Möglichkeiten. ■
Variablen Durch die Definition einer Variablen reserviert der Programmierer Speicherplatz für Werte eines bestimmten Datentyps. Im weiteren Verlauf kann er in der Variablen Werte besagten Typs abspeichern (natürlich immer nur einen Wert zurzeit) oder den jeweils aktuellen Wert abfragen. int eineVar; // Variablendefinition eineVar = 5 * 2; // Zuweisung System.out.println(eineVar); // Abfrage
■
Literale Literale sind »wörtliche« Konstanten: 3 14.5 "Dies ist ein String-Literal"
■
Symbolische Konstanten Symbolische Konstanten sind Konstanten, die mit einem Namen verbunden wurden, der im weiteren Quelltext als Alias für die Konstante verwendet werden kann. Symbolische Konstanten werden mit dem Schlüsselwort final definiert. final double PI = 3.14159265358979323846; ... umfang = 2 * PI * radius;
■
Rückgabewerte von Methoden Der Aufruf einer Methode, die als Ergebnis einen Wert zurückliefert (siehe Kapitel 8.3, Abschnitt »Rückgabewert«), kann als Repräsentant des Ergebniswerts verwendet werden. // kreisUmfang(double r) sei eine Methode, die zum // angegebenen Radius den zugehörigen Kreisumfang
112
Das Java-Datenmodell
// berechnet umfang = kreisUmfang(radius);
Allen vier Formen ist gemeinsam, dass sie typisiert sind, d.h., jede in einem Programm definierte Variable, jede Konstante (wörtlich oder symbolisch) und jeder Rückgabewert gehört einem bestimmten Datentyp an und kann nur Werte dieses Datentyps repräsentieren. 1
Dies wirft die Frage auf, welche Datentypen es in Java gibt und wie das Java-Typenkonzept es dem Programmierer gestattet, mit den unterschiedlichsten Daten – von einfachen Zahlen über Strings bis zu Adressen, Bildern, Sound etc. – zu arbeiten?
5.1.2
2 3
Das Typenkonzept
Während der Computer letzten Endes nur einen einzigen Datentyp, nämlich die Bitfolge, kennt, erfreut Java den Programmierer mit immerhin acht elementaren Datentypen sowie der Möglichkeit, sich auf der Basis der elementaren Datentypen eigene, komplexere Datentypen selbst zu definieren. Bestimmte, häufig benötigte komplexe Datentypen sind bereits vordefiniert und finden sich in der Java-Standardbibliothek wieder – beispielsweise Typen für Datums- und Zeitangaben, Strings oder Zufallszahlen. Der StringTyp nimmt dabei eine gewisse Sonderstellung ein, denn er ist so stark in die Sprache integriert, dass er fast schon wie ein elementarer Typ verwendet werden kann. Doch eins nach dem anderen.
5 6 7 Abbildung 5.1: Die Java-Datentypen
Java-Datentypen
Einfache Typen
4
Komplexe Typen
8 9 10
Numerische Typen
11 Integer boolean
Gleitkomma
char
float
byte
double
Array Aufzählung
Schnittstelle Klasse
Name [] enum Name
12
class Name
interface Name
13
short int long
14
113
Daten und Datentypen
Die elementaren Datentypen Die elementaren Datentypen sind fest in der Sprache verankert. Für jeden elementaren Datentyp gibt es in Java: ■ ■ ■
■ ■
ein eigenes Schlüsselwort (zur Definition von Variablen und symbolischen Konstanten des Typs), eine eigene Syntax für Literale, einen festen Wertebereich (Ergibt sich aus dem Codierungsverfahren, nach dem die Werte des Typs binär codiert werden, und der Größe des Speicherbereichs, der für Variablen des Typs reserviert wird.), einen Satz vordefinierter Operationen (samt passender Operatoren), die auf die Werte des Datentyps angewendet werden dürfen, eine sogenannte Wrapper-Klasse, die zum einen nützliche Methoden und Konstanten für die Arbeit mit den Daten bereitstellt, zum anderen eine Objektrepräsentation der elementaren Daten erlaubt (Mehr zu den Wrapper-Klassen in Abschnitt 5.8.)
Die Programmierung mit elementaren Datentypen gestaltet sich dadurch extrem einfach: // Variablendefinition mit Schlüsselwörtern für // Datentypen int wert1, wert2; int summe, differenz, produkt, quotient; // Zuweisung von literalen Werten wert1 = 10; wert2 = 5; // Anwendung erlaubter Operationen summe = wert1 + wert2; differenz = wert1 – wert2; produkt = wert1 * wert2; quotient = wert1 / wert2;
Welche elementaren Datentypen gibt es? Tabelle 5.1 stellt sie Ihnen kurz vor; ausführlichere Portraits der elementaren Datentypen folgen in Abschnitt 5.5. Tabelle 5.1: Die elementaren Datentypen
Typ
Größe
Beschreibung und Wertebereich
Beispiele
boolean
1
für boolesche Wahrheitswerte (wie sie in Bedingungen von Schleifen und Verzweigungen verwendet werden)
true false
true (wahr) und false (falsch) char
2
für einzelne Zeichen Wertebereich sind die ersten 65536 Zeichen des Unicode-Zeichensatzes
114
'a' '?' '\n'
Das Java-Datenmodell
Typ
Größe
Beschreibung und Wertebereich
Beispiele
byte
1
ganze Zahlen sehr kleinen Betrags
-3 0 98
-128 bis 127
short
2
ganze Zahlen kleinen Betrags -32.768 bis 32.767
4
int
Standardtyp für ganze Zahlen -2147483648 bis 2147483647
long
8
für sehr große ganze Zahlen
double
8
1 2
-3 0 1000000
3 4
+/-3,40282347*1038
123.56700 -3.5e10
5
Standardtyp für Gleitkommazahlen mit größerer Genauigkeit
123.456789 12005.55e-12
6
9223372036854775807 4
-3 0 1205
-3 0 1000000000000
-9223372036854775808 bis
float
Tabelle 5.1: Die elementaren Datentypen (Forts.)
für Gleitkommazahlen geringer Genauigkeit
+/-1,79769313486231570*10308
7 Anders als in C oder C++, wo die tatsächliche Größe der elementaren Datentypen von Plattform zu Plattform variieren kann (die Sprachspezifikation gibt nur einzuhaltende Mindestwerte vor), sind in Java alle Parameter, auch die Größe, verbindlich festgelegt. Nur so kann sichergestellt werden, dass ein Programm auf beliebigen Plattformen mit gleichen Ergebnissen ausgeführt wird (und nicht etwa Fehler produziert, weil der int-Typ auf einer Plattform nur 2 Byte groß ist und die Zahl 100000 nicht aufnehmen kann).
INFO
8 9 10
Die komplexen Datentypen Unter diesem Titel haben wir die Datentypen zusammengefasst, die nicht in der Sprache integriert sind. Sie müssen explizit in einer für den Compiler verständlichen Syntax definiert werden. Sie teilen sich in drei Kategorien auf: ■
11
12
Klassen Die mit Abstand bedeutendste Kategorie selbst definierter Datentypen bilden die Klassen. Sie gestatten nicht nur dem Programmierer eigene Datentypen auf der Basis elementarer und/oder bereits definierter komplexer Datentypen zu erzeugen und – in Form von Methoden – mit typspezifischen »Operationen« auszustatten, sie bilden auch die Basis der objektorientierten Programmierung!
13 14
115
Daten und Datentypen
Eine Schnelleinführung in die Programmierung mit Klassen finden Sie in Kapitel 2.3. Ausführlich und umfassend werden die mit Klassen verbundenen Konzepte in Kapitel 8 vorgestellt. ■
■
■
Schnittstellen Schnittstellen ersetzen in Java das von manchen objektorientierten Sprachen angebotene Konzept der Mehrfachvererbung. Mit ihrer Hilfe können Klassen unabhängig von ihren Vererbungslinien kategorisiert und typisiert werden. Das Verfahren ist einfach zu implementieren, der praktische Nutzen enorm. Von Schnittstellentypen können keine Variablen deklariert werden! Die Programmierung mit Schnittstellen wird in Kapitel 15.2 behandelt. Arrays Arrays sind Typen, die eine Folge von Werten (oder Objekten) eines Typs repräsentieren. Wenn Sie beispielsweise 100 Messwerte einlesen und bearbeiten müssen, ist es einfacher, diese in einem Array von 100 double-Werten zu speichern als in 100 explizit zu deklarierenden double-Variablen. Arrays werden in Kapitel 9 behandelt. Aufzählungen Aufzählungen sind Datentypen, deren Wertebereich aus einer Gruppe von Konstanten besteht. Welche Konstanten zu einer Aufzählung gehören, legt der Programmierer selbst fest. Typische Beispiele sind Aufzählungen für Monate, Dateiflags etc. Aufzählungen werden in Kapitel 9.5 behandelt.
Wert- und Referenztypen Allen komplexen Datentypen gemeinsam ist, dass ihre Objekte nicht wie die Werte elementarer Datentypen im Speicherbereich ihrer Variablen, sondern frei im Arbeitsspeicher angelegt werden. In den Variablen der komplexen Datentypen wird lediglich ein Verweis (auch Referenz genannt) auf das Objekt gespeichert. Die komplexen Datentypen werden daher auch als Verweis- oder Referenztypen bezeichnet und von den elementaren oder Werttypen unterschieden. Objekte von Referenztypen werden mit dem new-Operator erzeugt: DemoKlasse obj = new DemoKlasse(); int[] einArray = new int[100];
// Erzeugung eines Klassenobjekts // Erzeugung eines Array-Objekts
Das Objekt wird im Speicher erzeugt, der new-Operator liefert die Referenz auf das Objekt zurück, die in einer passenden Variablen gespeichert werden kann. Dies hat Folgen für die Programmierung. Wenn Sie einer Variablen eines elementaren Datentyps, nennen wir sie b, eine Variable a zuweisen, kopieren Sie den Wert aus b nach a. Danach gibt es zwei Variablen a und b mit eigenen Werten, die für den Moment identisch
116
Das Java-Datenmodell
sind, die aber unabhängig voneinander verändert und bearbeitet werden können: int int b = b =
a = 3; b; a; 12;
// a = 3 // a = 3
b = 3 b = 12
1
Wenn Sie hingegen einer Variablen eines Referenztyps, nennen wir sie wieder b, eine Variable a zuweisen, kopieren Sie die Referenz aus a nach b. Danach gibt es zwei Variablen a und b, die als Werte Referenzen enthalten! Da die Referenzen momentan identisch sind, verweisen sie auf ein und dasselbe Objekt, das nun sowohl über a als auch über b bearbeitet werden kann. class Demo { feld = 0; void aendern() { feld = 1; } } Demo a = new Demo(); Demo b; b = a; // a.feld = 0 b.aendern(); // a.feld = 1
2 3 4
5 b.feld = 0 b.feld = 1
6
Ebenso wie wir elementare Variablen als Repräsentanten der in ihnen gespeicherten Werte ansehen, betrachten wir Objektvariablen als Repräsentanten der Objekte, auf die sie verweisen. Dies ist selbstverständlich statthaft, darf jedoch nicht dazu führen, dass wir vergessen, dass der Wert einer Objektvariablen eine Referenz ist (und nicht das Objekt) und dass beim Zuweisen von Objektvariablen Referenzen – und nicht Objekte – kopiert werden.
7 HALT
8 9
Gleiches gilt für Vergleiche mit == und !=, die für Objektvariablen nur die Referenzen, nicht die Objekte vergleichen. Der Vergleich obj1 == obj2 liefert daher genau dann true zurück, wenn die Referenzen in obj1 und obj2 identisch sind und auf ein und dasselbe Objekt verweisen.
10 11
Die Programmierung mit Referenztypen weist noch weitere Eigenheiten auf, mit denen wir uns an gegebener Stelle noch eingehender beschäftigen werden, siehe ■ ■ ■
12
Programmieren mit Referenzen (in Kapitel 6.2), Speicherbelegung (in Kapitel 10.2, Abschnitt »Lebensdauer von Objekten«), Programmieren mit Objekten (in Kapitel 17).
13 14
117
Daten und Datentypen
Strings und andere vordefinierten Datentypen In der Standardbibliothek von Java, verteilt auf zahlreiche Pakete, sind eine Reihe nützlicher Klassen (und Schnittstellen) vordefiniert. Tabelle 5.2: Häufig benötigte Klassen aus der Standardbibliothek
Programmierung mit
Paket / Klasse
Buchkapitel
Strings
java.lang.String
5.6
mathemat. Funktionen
java.lang.Math
6.3.7, »Die Klasse Math«
Zufallszahlen
java.util.Random
6.3.8, »Zufallszahlen«
Datums- u. Zeitangaben
java.util.Date java.text.DateFormat
28.2
Container-Klassen
java.util.*
29
GUI-Programmierung
java.awt.*
Teil IV
Swing-Steuerelemente
javax.swing.*
Teil IV
5.1.3
An Datentypen gebundene Konzepte
Die Bedeutung des Datentyps kann nicht hoch genug eingeschätzt werden. Sein Regiment ist unausweichlich – jeder Wert in Java hat einen festen, definierten Datentyp – und rigide: ■
■
■ ■
Er legt fest, wie die Werte binär codiert werden. (Im Arbeitsspeicher müssen die Werte ja als binäres Bitmuster abgelegt werden. Die Codierung eines Zeichens in eine Bitfolge erfolgt dabei nach anderen Regeln als die Codierung einer ganzen Zahl oder einer Gleitkommazahl.) Er legt fest, wie groß der Speicherbereich sein muss, der für Werte und Variablen seines Typs zu reservieren ist. (Eine Gleitkommazahl benötigt mehr Speicher als ein einzelnes Zeichen und eine Strukturvariable benötigt meist mehr Speicher als eine Gleitkommazahl.) Er legt fest, welche Operationen auf den Werten erlaubt sind. (Beispielsweise können ganze Zahlen multipliziert werden, nicht aber Strings.) Er legt fest, in welche anderen Typen die Werte umgewandelt werden können und wie diese Umwandlung auszusehen hat.
5.2
Variablen
Eine Variable ist ein Name für einen Speicherbereich, in den ein Datenobjekt abgelegt werden kann (beispielsweise eine Integer-Zahl, eine Gleitkommazahl, ein String, etc.). Über den Namen der Variablen kann auf den Speicherbereich zugegriffen werden, um die sich dort befindenden Daten zu lesen oder Daten dorthin zu schreiben. Der Compiler legt zu diesem Zweck eine Symboltabelle an, in der zu jedem Variablennamen die Anfangsadresse des
118
Variablen
zugehörigen Speicherbereichs vermerkt ist. Bei der Kompilation kann er dann jedes Vorkommen eines Variablennamens durch die passende Adresse ersetzen. Für das Anlegen dieser Symboltabelle ist es aber notwendig, dass jede Variable vor ihrer Verwendung deklariert wird. Die im Folgenden vorgestellten Syntaxformen gelten für alle Arten von Variablen (lokale Variablen, Parameter, Felder, Array-Elemente). Unterschiede werden explizit angegeben oder im zugehörigen Kontext in den späteren Kapiteln erläutert.
5.2.1
1 TIPP
2
Deklaration
In Java muss jede Variable vor der Verwendung deklariert werden: typ variablenname;
typ. Erlaubt ist jeder elementare oder selbst definierte Datentyp. variablenname. Bezeichner der einzurichtenden Variablen. Der Bezeichner ist unter Einhaltung der im Abschnitt »Regeln für die Namensgebung« aufgelisteten Regeln frei wählbar.
Beachten Sie, dass jede Variablendeklaration mit einem Semikolon abgeschlossen wird und dass Java zwischen Groß- und Kleinschreibung unterscheidet (summe und Summe demnach unterschiedliche Variablen darstellen).
Java unterscheidet zwischen Groß- und Kleinschreibung in Bezeichnern
4
5 6
HALT
7
Mithilfe des Komma-Operators können mehrere Variablen des gleichen Datentyps hintereinander definiert werden: typ
3
8
var1, var2, var3;
Sie sollten diese Schreibweise jedoch nur für Variablen verwenden, die bedeutungsmäßig zusammengehören, beispielsweise:
9
var vorname, nachname; var x, y;
10
Auf diese Weise sorgen Sie dafür, dass Ihr Code übersichtlich und leicht verständlich bleibt. Wozu im Übrigen auch die kurze Kommentierung wichtiger Variablen beiträgt.
11
Beispiele für gültige Deklarationen sind: int i; int i, j, n; double bruch; String name;
12 13
// Name des eingeloggten Benutzers
14
119
Daten und Datentypen
Deklaration und Definition EXKURS
Neben dem Begriff der Deklaration hört man auch häufig von Definitionen. Beide Begriffe werden in der Programmierung gelegentlich (durchaus zu Recht) synonym gebraucht, betonen aber an sich unterschiedliche Dinge. Deklaration meint, dass ein Bezeichner (meist einer Variablen) beim Compiler angemeldet, sprich bekannt gemacht wird. Definition bedeutet, dass ein Element beschrieben und mit Speicher verbunden wird. In Java sprechen wir in der Regel von Variablendeklarationen und Methoden- und Typdefinitionen. Hier wird bereits deutlich, dass die Begriffe nicht wirklich scharf zu trennen sind: in Java geht nämlich jede Variablendeklaration auch mit einer Speicherreservierung einher (ist somit auch eine Definition), und jede Definition meldet das definierte Element beim Compiler an (ist demnach auch eine Deklaration).2
5.2.2
Regeln für die Namensgebung
In der Wahl Ihrer Variablennamen (wie auch anderer Bezeichner, beispielsweise Methoden- oder Klassennamen, siehe nachfolgende Kapitel) sind Sie gänzlich frei, solange Sie sich an folgende Regeln halten: ■
■ ■
Erlaubt sind Groß- und Kleinbuchstaben (A – Z und a – z), die Ziffern von 0 bis 9, der Unterstrich _ und das Dollarzeichen (letzteres jedoch nur um automatisch generierten Code zu unterstützen). Andere Zeichen, insbesondere Leerzeichen, Punkte, und sonstige Sonderzeichen sind nicht erlaubt. Das erste Zeichen muss ein Buchstabe oder ein Unterstrich sein. Die in Java definierten Schlüsselwörter (siehe Anhang zur Syntaxübersicht) dürfen nicht als Bezeichner verwendet werden.
Tatsächlich ist Java sogar großzügiger als hier angegeben, da es mit dem Unicode-Zeichensatz arbeitet und als »Buchstaben« auch nationale Eigenheiten wie eigene Zeichendarstellungen oder Umlaute zulässt. Grundsätzlich möchten wir aber von der Verwendung solcher Zeichen abraten. So könnten Sie beispielsweise für bestimmte Variablen griechische Zeichen verwenden. Doch abgesehen davon, dass die Eingabe dieser Zeichen umständlich ist, müssen Sie zusätzlich noch in Kauf nehmen, dass diese Zeichen auf manchen Rechnern und in Nicht-Unicode-Editoren nicht angezeigt werden können. Auch die deutschen Umlaute sollten Sie – ganz besonders in Klassennamen – meiden. Der kompilierte Klassencode muss nämlich in einer Klassendatei abgespeichert werden, die den Namen der Klasse trägt. Tauchen in dem Klassennamen Umlaute oder sonstige Zeichen auf, die auf Betriebssystemebene nicht unterstützt werden (man denke nur an die DOS-Konsole), können die Programme nicht ausgeführt werden.
2
120
In C++ ist die Trennung von Definition und Deklaration schärfer, da hier Variablen, Funktionen und Methoden im strengsten Sinne deklariert werden können (reine Einführung des Bezeichners ohne Definition oder Speicherreservierung).
Variablen
Beherzigen sollten Sie bei der Vergabe von Variablennamen auch die folgenden Konventionen: ■
■ ■
Beginnen Sie die Namen stets mit Kleinbuchstaben; setzt sich der Name aus mehreren Wörtern zusammen, sollten die nachfolgenden Wörter mit Großbuchstaben anfangen. Geben Sie (wichtigen) Variablen informative Namen, die auf die Bedeutung der in der Variablen gespeicherten Daten hinweisen. Lokale Hilfsvariablen, wie Schleifenvariablen oder Variablen zum Abspeichern von Zwischenergebnissen, erhalten kurze Namen.
1 2
Gültige Variablennamen sind: int int int int
3
n; option1; _name; alterWert;
4
Während die folgenden Namen nicht akzeptiert werden: int 123; int case;
5.2.3
5
// ungültiger Name, fängt mit Ziffer an // Schlüsselwort
6
Initialisierung
Wenn Sie möchten, können Sie Ihren Variablen bereits bei der Deklaration einen Anfangswert zuweisen. Man bezeichnet dies als Initialisierung:
7
int alter = 24; int i = 0, j = 1, n = 2;
8
Sie müssen nicht jede Variable initialisieren. Wenn es jedoch für eine Variable einen sinnvollen Anfangswert gibt, sollten Sie die Variable auch mit diesem Wert initialisieren.
9
Welchen Anfangswert haben nicht initialisierte Variablen?
10
Hier ist zwischen Parametern, Feldern von Klassen und lokalen Variablen zu unterscheiden. Die Parameter werden natürlich mit den Argumenten aus dem Aufruf initialisiert. Wie aber sieht es bei den Feldern und lokalen Variablen aus? ■
■
11
Felder werden, sofern der Programmierer keinen Initialisierungswert vorgibt, automatisch mit dem Nullwert des zugehörigen Datentyps initialisiert. (Gleiches gilt übrigens auch für die Elemente von Arrays.) Lokale Variablen werden hingegen nicht automatisch initialisiert!
12 13 14
121
Daten und Datentypen
Tabelle 5.3: Nullwerte
Datentyp
Nullwert
boolean
false
Integer-Typen
0
Gleitkommatypen
0.0
char
'\0' ('\u0000')
Referenztypen
null
Dass lokale Variablen nicht automatisch initialisiert werden, hat Konsequenzen für die Programmierung. Die Java-Spezifikation fordert nämlich ein »definitives Assignment« für Variablen. Definitive Assignment »Definitive Assignment« bedeutet, dass einer Variablen zuerst ein definierter Wert zugewiesen werden muss, bevor der Wert der Variablen abgefragt werden kann. Während Sie in C++ den Wert einer Variablen, der noch kein definierter Wert zugewiesen wurde, durchaus abfragen können ... int lokVar;
cout , >>>, >>= ++ --
Zuweisung des Ergebniswerts eines Ausdrucks an eine Variable. Der reine Zuweisungsoperator kann mit verschiedenen anderen Operatoren gepaart werden. Sonderformen der Zuweisung sind Inkrement und Dekrement
Vergleiche
!= == < > = ?:
Vergleich von Zahlen und Ausdrücken
Aussagenlogik
! && || & |
Zur logischen Verknüpfung von booleschen Ausdrücken (siehe Abschnitt »Die logischen Operatoren« in Kapitel 7.1 )
Bitmanipulation
& ^ | ~ > >>>
Für Operationen auf Bitebene, beispielsweise Invertierung aller Bits einer Variablen
Typumwandlung und -identifizierung
() instanceof
Zur expliziten Typumwandlung
Sonstige
() , []
Methodenaufruf
6.1
^
Bedingungsoperator
Typidentifizierung
Aneinanderreihung von Ausdrücken Indizierung
Allgemeines
Bevor wir näher auf die einzelnen Operatoren eingehen, ein paar allgemeine Anmerkungen vorab.
150
Allgemeines
6.1.1
Unär, binär, ternär
Nach der Anzahl ihrer Operanden werden die Operatoren als ■ ■ ■
unär (ein Operand), binär (zwei Operanden) und ternär (drei Operanden)
1
klassifiziert. (Den einzigen ternären Operator Javas, den Bedingungsoperator, werden Sie erst in Kapitel 7.2 kennenlernen.) 2
6.1.2
Binäre numerische Promotionen 3
Die meisten Java-Operatoren sind binär, so z. B. auch die arithmetischen Operatoren für Addition, Subtraktion und so weiter. int short int ... c = a
4
a = 120000; b = 3; c; + b;
5
// Addition
Auf CPU-Ebene können die binären Operationen aber nur dann durchgeführt werden, wenn die beiden Operanden exakt den gleichen Typ haben. Obige Operation, bei der ein short-Wert (b) und ein int-Wert (a) addiert werden, ist an sich also gar nicht zulässig. Besonders streng typisierte Sprachen würden hier von dem Programmierer erwartet, dass er die Typen selbst angleicht, etwa mithilfe einer Typumwandlung (Cast): c = a + (int) b, doch derart strenge Sprachen sind uns nicht bekannt. Die meisten Sprachen, darunter auch Java, gleichen die Typen der beiden Operanden gemäß den binären numerischen Promotionen an.
6 7 8 9
Die binären numerischen Promotionen legen fest, wie der Compiler die Typen der Operanden ohne größere Informationsverluste angleichen kann. Eine mögliche, wenn auch nicht sehr effiziente Vorschrift zur Durchführung einer numerischen Promotion könnte lauten: 1.
2.
10
Führe eine integrale Promotion durch, d.h., wandele alle char-, byteund short-Operanden in int-Operanden um. Hierbei entsteht kein Informationsverlust. Bestimme, welcher Operand gemäß der Folge int < long < float < double den »größten« Typ besitzt, und wandle den anderen Operanden in diesen Typ um. Hierbei kann es zu Informationsverlusten kommen, wenn Operanden vom Typ int oder long in float oder double umgewandelt werden.
11
12 13 14
Die integrale Promotion wird auch bei char-, byte- und short-Operanden unärer Operatoren vorgenommen. INFO
151
Operatoren und Ausdrücke
6.1.3
Ausdrücke
Ausdrücke repräsentieren immer Werte. Im einfachsten Fall besteht der Ausdruck aus einem Literal, einer Variablen oder einem Methodenaufruf. In diesen Fällen ist der Wert des Ausdrucks gleich dem Wert des Literals, der Variablen bzw. dem Rückgabewert der Methode: x = y; // Ausdruck aus einer Variablen (y) x = 1; // Ausdruck aus einem Literal (1) quadrat = Math.pow(x,2); // Ausdruck aus Rückgabewert einer Methode
Ausdrücke können auch durch Verknüpfung von Werten mit Operatoren gebildet werden. j = i + n; if (x < 100) eineMethode(x – y)
// Ausdruck aus Operator und zwei Operanden // boolescher Ausdruck in Bedingung // Ausdruck als Argument einer Methode
Da Operatoren jede Art von Wert als Operand akzeptieren und jeder Ausdruck einen Wert darstellt, können mithilfe von Operatoren beliebig komplexe Ausdrücke aus Teilausdrücken gebildet werden. x = x + 3 * x; // Ausdruck aus mehreren Operatoren x = (x + 1) * ( x – 1); // geklammerter Ausdruck
6.1.4
Anweisungen
Ausdrücke stehen in der Regel nicht allein, sondern sind Teil einer Anweisung, beispielsweise ■
einer Zuweisung, j = i + n;
■
einer Kontrollanweisung (siehe Kapitel 7), if (x < 100) { x = 100; }
■
eines Methodenaufrufs (siehe Kapitel 8.3). eineMethode(x – y);
Einzelne Anweisungen können mithilfe der geschweiften Klammern zu einem Anweisungsblock zusammengefasst werden: { ersteAnweisung; zweiteAnweisung; dritteAnweisung; }
Anweisungen enden mit einem Semikolon. Die Ausnahme von dieser Regel bilden die Kontrollanweisungen, die auch mit einem Anweisungsblock enden können.
152
Operationen auf allen Datentypen
Die leere Anweisung ; Ein einzelnes Semikolon wird ebenfalls als Anweisung – als leere Anweisung – gedeutet, wenn das Semikolon an einer Stelle steht, wo eine Anweisung erlaubt ist. System.out.println("Hallo");;
1
Hier stehen zwei Anweisungen in einer Zeile. Die erste Anweisung lautet System.out.println("Hallo"); und gibt den String »Hallo« aus, die zweite Anweisung ist eine leere Anweisung, die nichts tut.
2
Dass Sie Anweisungen mit einem Semikolon abschließen müssen, kann man noch damit erklären, dass der Compiler dadurch das Ende einer Anweisung sicher erkennen kann. Doch welchen Sinn soll die leere Anweisung haben? Der Grund ist, dass es in Java Konstruktionen gibt, in denen Anweisungen zwingend angegeben werden müssen, damit die Konstruktion syntaktisch korrekt ist. Es gibt aber Fälle, in denen man an den betreffenden Stellen eigentlich gar keine Anweisungen ausführen lassen möchte (siehe for-Schleife in Kapitel 7.3). Für diese Fälle ist die leere Anweisung gedacht.
3 4 5
Für Programmiereinsteiger wird die leere Anweisung allerdings meist zum Fallstrick – beispielsweise wenn Sie sie hinter eine if-Bedingung setzen: if (x < 100); { x = 100; }
6.2
// Fehler!
6
HALT
7
Operationen auf allen Datentypen
8
Insgesamt vier Operatoren der Sprache sind für alle Datentypen definiert. 9
6.2.1
Der Zuweisungsoperator =
Der Zuweisungsoperator weist seinem linken Operanden den Wert des rechten Operanden zu.
10
Damit dies möglich ist, muss der linke Operand auf eine Adresse im Speicher verweisen, in die geschrieben werden kann. Mit anderen Worten: der Compiler erwartet links den Namen einer Variablen (die selbstredend nicht als final deklariert sein darf). Der rechte Operand kann dagegen ein beliebiger Ausdruck sein, der einen Wert darstellt – also beispielsweise ein Literal, eine Variable, der Aufruf einer Methode, die einen Wert zurückliefert, oder eine Kombination aus Operatoren und eben genannten Werten.
11
12 13
int bestand, eingang; bestand = 10; // Zuweisung eines Literals eingang = 20; bestand = bestand + eingang; // Zuweisung eines arithm. Ausdrucks
14
153
Operatoren und Ausdrücke
Für Referenztypen kopiert der Zuweisungsoperator die Referenzen, nicht die Objekte, auf die Referenzen verweisen! HALT
L- und R-Werte Ausdrücke, die als Adresse eines beschreibbaren Speicherbereichs interpretiert werden können, bezeichnet man in der Programmierung allgemein als L-Werte (das L steht hierbei für »left« und spielt darauf an, dass diese Ausdrücke links einer Zuweisung stehen dürfen). In Java ist der Begriff an sich überflüssig, da nur Variablennamen L-Werte darstellen können. In Programmiersprachen, die direkte Zeigermanipulationen erlauben, sind jedoch meist noch andere Ausdrücke als L-Werte erlaubt (beispielsweise ++zeiger). Das Pendant zu den L-Werten sind die R-Werte, die einen Wert darstellen und in Zuweisungen rechtsseitig auftauchen. Literale und Methodenaufrufe sind Beispiele für Ausdrücke, die nur R-Werte darstellen können, während (nicht-final) Variablen sowohl als L- wie auch als R-Wert fungieren können. (Der Compiler entscheidet dann je nach Kontext, ob in die Variable geschrieben oder ihr Wert abgefragt werden soll.)
6.2.2 Die Gleichheitsoperatoren == und != Mit dem Operator == können Sie prüfen, ob die Werte zweier Variablen gleich sind. Sind die Werte gleich, ist das Ergebnis true, ansonsten false. int aktStand = 113; boolean veraendert; ... veraendert = (aktStand == 113);
// true, wenn aktStand immer noch 113
Der Operator != prüft den umgekehrten Fall, nämlich ob zwei Werte verschieden sind. Wenn ja, liefert er als Ergebnis true, ansonsten false. int aktStand = 113; boolean veraendert; ... veraendert = (aktStand != 113);
// true, wenn aktStand nicht mehr 113
Die Gleichheitsoperatoren werden meist in Bedingungen von Schleifen und Verzweigungen eingesetzt (siehe Kapitel 7.1). Für Referenztypen vergleichen die Gleichheitsoperatoren die Referenzen, nicht die Objekte, auf die Referenzen verweisen! HALT
154
Operationen auf numerischen Datentypen
6.2.3 Der Typumwandlungsoperator (typ) Den Typumwandlungsoperator () haben Sie bereits in Kapitel 5.7 kennengelernt. Mit seiner Hilfe können Sie einen Wert vom Typ A in einen Wert vom Typ B umwandeln (im Englischen »casten« genannt). Voraussetzung ist allerdings, dass eine solche Typumwandlung zulässig und vom Compiler durchführbar ist:
1
int iZahl = 124; short sZahl = (short) iZahl;
2
6.3
Operationen auf numerischen Datentypen 3
Auch wenn man kein Mathematiker sein muss, um programmieren zu können, der Umgang mit Zahlen ist für die Programmierung von besonderer Bedeutung. So verwundert es nicht, dass Java breite Unterstützung für die Programmierung mit Zahlen bietet: ■ ■ ■ ■ ■
4
ein breites Spektrum an Operatoren, die Klasse Math mit ihren statischen Methoden, die Klasse Random zur Erzeugung von Zufallszahlen, die Wrapper-Klassen für die numerischen Datentypen (siehe Kapitel 5.8) die Klassen BigDecimal und BigInteger für Zahlen mit beliebiger Genauigkeit (siehe Kapitel 11.2).
6.3.1
5
6 7
Vorzeichen
8
Mit den unären Operatoren + und – können Sie arithmetischen Variablen ein Vorzeichen zuweisen. Werte ohne Vorzeichen sind automatisch positiv. int x, y; x = +3; y = -x;
9
// gleichbedeutend mit x = 3;
10
6.3.2 Die arithmetischen Operatoren
11
Mit den arithmetischen Operatoren können Sie, wie es der Name vermuten lässt, einfache arithmetische Operationen durchführen, wie sie auch in anderen Programmiersprachen oder einfachen mathematischen Gleichungen üblich sind. Operator
Bedeutung
Beispiel
+
Addition
var = 3 + 4;
// var = 7;
-
Subtraktion
var = 3 - 4;
// var = -1;
*
Multiplikation
var = 3 * 4;
// var = 12;
12 Tabelle 6.2: Die arithmetischen Operatoren
13 14
155
Operatoren und Ausdrücke
Tabelle 6.2: Die arithmetischen Operatoren (Forts.)
Operator
Bedeutung
Beispiel
/
Division
int var = 7 / 4; double var = 7.0 / 4.0;
// var = 1; // var = 1.75;
%
Modulo
var = 7 % 4;
// var = 3;
(Rest einer Division)
Zur Verwendung der arithmetischen Operatoren gibt es kaum etwas zu sagen, da sie im Wesentlichen ganz wie in der Mathematik eingesetzt werden – einschließlich der Regel, dass Punkt- vor Strichrechnung geht und man für eine andere Abarbeitungsreihenfolge Klammern setzen muss. Lediglich Division und Modulo sind einer näheren Betrachtung wert. Division Die Division liefert für Integer- und Gleitkomma-Operanden unterschiedliche Ergebnisse. Wenn zwei Integer-Operanden dividiert werden, ist das Ergebnis das ganzzahlige Ergebnis der Division: int quotient; quotient = 7 / 4;
// quotient = 1
Gehört einer der Operanden einem Gleitkommatyp an, wird eine Gleitkommadivision durchgeführt und das exakte Ergebnis der Division zurückgeliefert. double quotient; quotient = 7.0 / 4.0;
// quotient = 1.75
Auch die Division durch Null wird unterschiedlich gehandhabt. Bei der Integer-Division führt die Division durch Null zur Auslösung einer ArithmeticException. Die Gleitkommadivision liefert als Ergebnis unendlich, die durch die Gleitkomma-Konstante NEGATIVE_INFINITY repräsentiert wird. double quotient = 13.65 / 0.0; if( Double.isInfinite(quotient) ) System.out.println("Unendlichkeit heisst in Java: " + quotient);
Beachten Sie, dass Sie keine direkten Vergleiche gegen Double.NEGATIVE_ INFINITY (oder Double.POSITIVE_INFINITY) vornehmen können, sondern die Methode isInfinite() aufrufen müssen. Der obige Code erzeugt die Ausgabe: Infinity
TIPP
156
Um eine exakte Gleitkommadivision für Integer-Operanden zu erzwingen, genügt es, einen der Operanden in einen Gleitkommatyp zu verwandeln. Der zweite Operand wird gemäß den binären numerischen Promotionen automatisch angepasst.
Operationen auf numerischen Datentypen
int op1 = 7; op2 = 4; double quotient = (double) op1 / op2;
Modulo Der Modulo-Operator liefert den ganzzahligen Rest einer Ganzzahldivision. int rest; rest = 7 % 4;
1 // rest = 3
Wenn a und b zwei Integer sind, gilt demnach: (a/b)*b+(a%b) gleich a.
2
Und wenn a und b zwei Gleitkommazahlen sind? Dann führt der Compiler die Modulo-Operation so durch, dass das Ergebnis einer Integer-ModuloOperation entspricht.
3
6.3.3 Inkrement und Dekrement
4
Zwei in der Programmierung häufig benötigte Operationen für Zahlen sind die Erhöhung beziehungsweise Verminderung um den Wert 1.
5
Hierfür gibt es in Java zwei spezielle Operatoren: ++ und --.
6
Die Anweisung ++zaehler;
setzt den Wert der Variablen zaehler um 1 hoch.
7
Die Erhöhung um 1 bezeichnet man auch als Inkrement. Die Anweisung
8 TIPP
--zaehler;
9
setzt den Wert der Variablen zaehler um 1 herab.
10
Die Verminderung um 1 bezeichnet man auch als Dekrement. Präfix- und Postfix-Notationen
TIPP
11
Das Besondere an den Operatoren ++ und -- ist, dass man sie auch in Ausdrücken (also auf der rechten Seite von Zuweisungen) verwenden kann:
12
var1 = 3 * ++var2;
und dass man sie sowohl vor als auch hinter den Namen der Variablen setzen kann:
13
++var; var++;
14
157
Operatoren und Ausdrücke
Zwischen beiden Formen gibt es einen wichtigen Bedeutungsunterschied: bei der Voranstellung (Präfix-Notation) erhöht der Operator den Wert der Variablen und gibt den neuen Wert an den umliegenden Ausdruck weiter, bei Nachstellung (Postfix-Notation) erhöht der Operator den Wert der Variablen, gibt aber den alten Wert an den umliegenden Ausdruck weiter.
■
■
Wenn einer der Inkrement- oder Dekrement-Operatoren alleine in einer Anweisung verwendet wird, ist es prinzipiell egal, ob Sie den Operator voran- oder nachstellen: ++var; var++;
// der Wert von var wird um 1 erhöht // der Wert von var wird um 1 erhöht
Nicht so in komplexeren Ausdrücken: int x = 0, y = 0; x x y x
TIPP
= = = =
++y * 5; 0; 0; y++ * 5;
// danach ist y = 1 und x = 5
// danach ist y = 1 und x = 0
Der nachgestellte Operator funktioniert so, dass intern eine temporäre Hilfsvariable eingerichtet wird. In dieser wird der aktuelle Wert der Variablen gespeichert. Erst danach wird der Wert der Variablen um 1 erhöht. Zurückgeliefert wird aber vom Operator der alte, in der Hilfsvariablen gespeicherte Wert.
6.3.4 Die Bit-Operatoren Wie Sie wissen (siehe Kapitel 2), werden alle Daten im Rechner binär, als Folge von Nullen und Einsen (Bits) dargestellt und abgespeichert. Mithilfe der Bit-Operatoren können Sie die Werte von Integer-Variablen auf der Ebene der Bits manipulieren – was in manchen Fällen zu sehr einfachen und schnellen Lösungen führt. Tabelle 6.3: Bit-Operatoren
158
Operator
Aufgabe
Beispiel
&
bitweise UND-Verknüpfung
0xFF & 223
|
bitweise ODER-Verknüpfung
0xFF | 0x20
^
bitweises XOR (exklusives ODER)
var1 ^ 0x0
~
bitweises Komplement
~var1
Rechtsverschiebung mit Erhalt des Vorzeichens
i >> 2
>>>
Rechtsverschiebung ohne Erhalt des Vorzeichens
j >>> 3
Operationen auf numerischen Datentypen
Die Bit-Operatoren sind nur für Integer-Typen (char, byte, short, int und long) definiert. Die Operanden unterliegen der integralen Promotion. HALT
bitweises UND: & Der &-Operator geht die Bits der beiden Operanden nacheinander durch. Immer wenn für eine Bitposition in beiden Operanden das jeweilige Bit auf 1 gesetzt ist, wird auch das entsprechende Bit im Ergebniswert auf 1 gesetzt. Andernfalls wird das Ergebnisbit auf 0 gesetzt.
1 2
0110 0001 & 1101 1111 -------------0100 0001
3
Dieser Operator kann beispielsweise eingesetzt werden, um Bits in einer Integer-Variablen gezielt zu löschen. int i = 0xFF; i &= 0xF0;
4
// i = 255 // setzt alle Bits außer den Bits 5 bis 8 auf 0 // i = 240
5
bitweises ODER: |
6
Der |-Operator geht die Bits der beiden Operanden nacheinander durch. Immer wenn für eine Bitposition in einem der Operanden das jeweilige Bit auf 1 gesetzt ist, wird auch das entsprechende Bit im Ergebniswert auf 1 gesetzt. Andernfalls wird das Ergebnisbit auf 0 gesetzt.
7
0110 0001 | 1101 1111 -------------1111 1111
8 9
Dieser Operator kann eingesetzt werden, um gezielt zusätzliche Bits in einer Integer-Variablen zu setzen. int i = 0xFF; i &= 0xF0; i |= 0xF;
// // // //
10
i = 255 setzt alle Bits außer den Bits 5 bis 8 auf 0 setzt die ersten 4 Bits auf 1 i wieder gleich 255
11
bitweises XOR: ^
12
Der ^-Operator geht die Bits der beiden Operanden nacheinander durch. Immer wenn für eine Bitposition in genau einem der Operanden das jeweilige Bit auf 1 gesetzt ist, wird auch das entsprechende Bit im Ergebniswert auf 1 gesetzt. Andernfalls wird das Ergebnisbit auf 0 gesetzt.
13
0110 0001 ^ 1101 1111 -------------1011 1110
14
159
Operatoren und Ausdrücke
Dieser Operator kann eingesetzt werden, um Bits umzuschalten (gesetzte Bits werden gelöscht und umgekehrt). int i = 0x8; i ^= 0xB;
// i = 8 // 0xB = 11 -> löscht 4. Bit // setzt Bit 1 und 2
bitweises Komplement: ~ Der ~-Operator für das bitweise Komplement geht die Bits des Operanden durch und invertiert die einzelnen Bits. Immer wenn für eine Bitposition im Operanden das jeweilige Bit auf 1 gesetzt ist, wird das entsprechende Bit im Ergebniswert auf 0 gesetzt und umgekehrt. ~ 0110 0001 -------------1001 1110
Für vorzeichenbehaftete Integer-Werte, die intern durch das 2n+1-Komplement codiert werden (siehe Kapitel 5.5), entspricht das Ergebnis des bitweisen Komplements einer Negation plus der Subtraktion von 1. int i = 50; i = ~i;
// i = -51
Linksverschiebung: >= 3;
4
7 8 9
// Division durch 8 (2^3) -> i = 9;
Rechtsverschiebung ohne Erhalt des Vorzeichens: >>> 10
Der >>>-Operator für die Rechtsverschiebung ohne Erhalt des Vorzeichens kopiert die Bits des ersten Operanden in den Ergebniswert – allerdings um so viele Positionen nach rechts gerückt, wie der zweite Operand angibt. Die links entstehenden Leerstellen werden mit Nullen aufgefüllt.
11
12
6.3.5 Die kombinierten Zuweisungen Neben dem einfachen Zuweisungsoperator = gibt es die zusammengesetzten Zuweisungsoperatoren, die gleichzeitig eine arithmetische oder bitweise Operation und eine Zuweisung durchführen. Die zusammengesetzten Zuweisungsoperatoren haben das Format:
13 14
=
wobei durch die Operatoren +, –, *, /, %, , >>>, &, ^ oder | ersetzt werden kann. 161
Operatoren und Ausdrücke
Die zusammengesetzten Zuweisungen verwenden den linken Operanden zweifach: zuerst als R-Wert für die eigentliche Operation (Addition +, Multiplikation * usw.) und danach als L-Wert, in den das Ergebnis der Operation geschrieben wird. bestand = eingang;
ist demnach nichts anderes als eine verkürzte Schreibweise für bestand = bestand eingang;
Diese Kurzform ist zunächst zwar etwas gewöhnungsbedürftig, spart aber dem Programmierer Tipparbeit und dem Anwender Laufzeit.
6.3.6 Die Vergleichsoperatoren Mit den vergleichenden Operatoren, die auch relationale Operatoren genannt werden, können Sie zwei numerische Werte vergleichen. Das Ergebnis einer Vergleichsoperation ist vom Datentyp boolean und somit entweder wahr (true) oder falsch (false). i i i i i i
== 1 != 1 < 1 1 >= 1
// // // // // //
gleich? ungleich? kleiner? kleiner gleich? größer? größer gleich?
Die Vergleichsoperatoren werden meist in Bedingungen von Schleifen und Verzweigungen eingesetzt und daher in Kapitel 7.1 näher vorgestellt.
6.3.7
Die Klasse Math
Bestimmte, häufig benötigte Rechenoperationen, wie das Ziehen einer Wurzel oder das Berechnen des Sinus eines Winkels, sind mithilfe der Operationen für die Grundrechenarten nur sehr schwer zu verwirklichen. Aus diesem Grunde gibt es die Klasse Math, die eine Reihe von mathematischen Konstanten, Funktionen und Operationen (Sinus, Betrag, Logarithmus, etc.) in Form von statischen Feldern und Methoden zur Verfügung stellt. Tabelle 6.6: Math-Felder für mathematische Konstanten
Tabelle 6.7: Statische MathMethoden für mathematische Funktionen2
Feld
Beschreibung
Math.E
Die Zahl E (Basis des natürlichen Logarithmus, 2.71828...)
Math.PI
Die Zahl p (3.14159...)
2
Methode
Beschreibung
typ Math.abs(typ x)
Absoluter Betrag
Math.acos(x)
Arkuskosinus Das Argument x muss zwischen –1.0 und 1.0 liegen.
162
Operationen auf numerischen Datentypen
Methode
Beschreibung
Math.asin(x)
Arkussinus Das Argument x muss zwischen –1.0 und 1.0 liegen.
Math.atan(x)
Arcustangens von x
Math.atan2(x,y)
Winkel des Punktes (x,y) relativ zur x-Achse
Tabelle 6.7: Statische MathMethoden für mathematische Funktionen2 (Forts.)
1
Dient der Umrechnung von kartesischen Koordinaten (x,y) in polare Koordinaten (radius, winkel).
Math.cbrt(x)
Kubische Wurzel
Math.ceil(x)
Rundet auf die nächste Ganzzahl auf.
Math.cos(x)
Kosinus
Math.cosh(x)
Hyperbolischer Kosinus
Math.cosh(x)
Hyperbolischer Kosinus
Math.exp(x)
Exponentialfunktion
2 3 4 5
Liefert ex zurück, wobei e die Basis des natürl. Logarithmus ist.
Math.expm1(x)
Liefert ex -1 zurück.
Math.floor(x)
Rundet auf die nächste Ganzzahl ab.
Math.hypot(x, y)
Liefert sqrt(x2 +y2) zurück.
Math.log(x)
Liefert den natürlichen Logarithmus ln x zurück.
Math.log10(x)
Logarithmus zur Basis 10
Math.log1p(x)
Liefert ln (x+1) zurück.
6 7 8 9
typ Math.max(typ x, typ y) Die größere der Zahlen x und y typ Math.min(typ x, typ y) Die kleinere der Zahlen x und y Math.pow(x, y)
Liefert die Potenz xy.
Math.random(x)
Liefert eine Zufallszahl größer gleich 0.0 und kleiner 1.0.
Math.rint(x)
Rundet x auf den nächsten ganzzahligen Wert auf oder ab.
long Math.round(double x)
Rundet das Argument auf den nächsten long-Wert auf oder ab.
int Math.round(float x)
Rundet das Argument auf den nächsten int-Wert auf oder ab.
Math.signum(x)
Vorzeichen
10 11
12 13
Liefert -1.0, 0.0 oder +1.0 zurück, je nachdem, ob das Argument kleiner, gleich oder größer Null ist.
14 2
Methoden mit der Typangabe typ sind für double, float, long und int definiert. (Können also dank integraler Promotion mit beliebigen numerischen Datentypen verwendet werden.) Methoden ohne Typangabe sind für double-Argumente und Rückgabewerte definiert.
163
Operatoren und Ausdrücke
Tabelle 6.7: Statische MathMethoden für mathematische Funktionen2 (Forts.)
Methode
Beschreibung
Math.sin(x)
Sinus
Math.sinh(x)
Hyperbolischer Sinus
Math.sqrt(x)
Quadratwurzel Das Argument muss positiv sein.
Math.tan(x)
Tangens
Math.tanh(x)
Hyperbolischer Tangens
Math.toDegrees(x)
Wandelt das Bogenmaß x in einen Winkel um.
Math.toRadians(x)
Wandelt den Winkel x in Bogenmaß um.
Neu in Java 6 Tabelle 6.8: Spezielle MathMethoden zur Programmierung mit Gleitkommazahlen3
3
Methode
Beschreibung
typ Math.copySign(typ x, typ y)
Liefert den Wert von x mit dem Vorzeichen von y zurück.
int Math.getExponent(typ x)
Liefert den Exponenten (zur Basis 2) des Arguments.
double Math.IEEEremainder( double x, double y)
Rest der Modulo-Operation für Gleitkommazahlen, gemäß IEEE 754 (liefert zum Teil andere Ergebnisse als der %-Operator).
typ Math.nextAfter(typ x, typ y)
Liefert die nächstgelegene repräsentierbare Gleitkommazahl neben x (in Richtung zur Gleitkommazahl y). Wenn y größer als x ist, gilt:
Math.nextAfter(x,y) == x+Math.ulp(x) typ Math.nextUp(typ x)
Liefert die nächstgrößere repräsentierbare Gleitkommazahl nach x.
typ Math.scalb(typ x, int faktor)
Liefert x * 2faktor zurück.
typ Math.signum(typ x)
Vorzeichen Liefert -1.0, 0.0 oder +1.0 zurück, je nachdem, ob das Argument kleiner, gleich oder größer Null ist.
typ Math.ulp(typ x)
Liefert den positiven Abstand zwischen dem Gleitkommaargument und der nächsthöheren Gleitkommazahl.
Verwendung Die Programmierung mit den Math-Methoden ist die Einfachheit selbst: Sie rufen die gewünschte Methode einfach über die Klasse Math auf, übergeben ihr die benötigten Parameter und erhalten das Ergebnis zurück. Um beispielsweise die Wurzel von 102355 zu berechnen, schreiben Sie: 3
164
Methoden mit der Typangabe typ sind für double und float definiert.
Operationen auf numerischen Datentypen
double wurzel; // Variable zur Aufnahme des Ergebnisses wurzel = Math.sqrt(102355);
Eine Import-Anweisung ist nicht nötig, da die Klasse Math im Paket java.lang definiert ist. Es steht Ihnen aber ab Java 5 frei, die statischen Methoden/Konstanten mit static import zu importieren, um die Elemente danach ohne das Math-Präfix verwenden zu können:
TIPP
1
import static java.lang.Math.sqrt; // Statischer Import der Methode // Math.sqrt() am Anfang der Datei ... double wurzel = sqrt(102355); // Zugriff ohne Math-Präfix in Code
Bei Verwendung der trigonometrischen Methoden ist zu beachten, dass diese Methoden als Parameter stets Werte in Bogenmaß (Radiant) erwarten. Beim Bogenmaß wird der Winkel nicht in Grad, sondern als Länge des Bogens angegeben, den der Winkel aus dem Einheitskreis (Gesamtumfang 2 π) ausschneidet: 1 rad = 360º/2 π; 1º = 2 π /360 rad.)
2
Die trigonometrischen Methoden
3 4
360 Grad entsprechen also genau 2 π, 180 Grad entsprechen 1 π, 90 Grad entsprechen 1/2 π. Wenn Sie ausrechnen wollen, was 32 Grad in Radiant sind, multiplizieren Sie einfach die Winkelangabe mit 2 * π und teilen Sie das Ganze durch 360 (oder multiplizieren Sie mit π und teilen Sie durch 180).
5
6
bogenlaenge = Math.PI/180 * grad;
7
Math stellt zur bequemen Umrechnung von Grad in Radiant und umgekehrt die Methoden toDegrees() und toRadians() zur Verfügung. Beachten Sie aber, dass diese Umrechnung nicht immer exakt ist. Gehen Sie also beispielsweise nicht davon aus, dass cos(toRadians(90.0)) exakt 0.0 ergibt.
8 9
6.3.8 Zufallszahlen 10
Die Erzeugung von Zufallszahlen mithilfe von Computern ist im Grunde gar nicht zu realisieren. Dies liegt daran, dass die Zahlen letzten Endes nicht zufällig gezogen werden, sondern von einem mathematischen Algorithmus errechnet werden. Ein solcher Algorithmus bildet eine Zahlenfolge, die sich zwangsweise irgendwann wiederholt. Allerdings sind die Algorithmen, die man zur Erzeugung von Zufallszahlen in Programmen verwendet, so leistungsfähig, dass man von der Periodizität der erzeugten Zahlenfolge nichts merkt.
11
12 13
Math.random() Wenn Sie in Ihrem Java-Code Zufallszahlen benötigen, beispielsweise zur Erzeugung zufälliger Zahlen für einen Rechentrainer, ist es am einfachsten, sich diese von der Methode Math.random() liefern zu lassen.
14
165
Operatoren und Ausdrücke
Jedes Mal, wenn Sie diese Methode aufrufen, liefert sie Ihnen einen Gleitkommawert zurück, der größer oder gleich 0.0 und kleiner als 1.0 ist. double zufallszahl; zufallszahl = Math.random();
Die Methode erzeugt die Zufallszahlen jedoch nicht selbst. Vielmehr greift sie auf eine weitere Klasse der Java-Standardbibliothek zurück: java.util. Random. Von dieser Klasse instanziiert sie ein Objekt, das ihr zukünftig als Zufallszahlengenerator dient und deren nextDouble()-Methode sie jedes Mal aufruft, wenn sie selbst aufgerufen wird. Die random()-Methode ist also nichts anderes als eine Convenience-Methode für die Erzeugung von Zufallszahlen. Wer mehr Kontrolle ausüben möchte, erzeugt sich seinen eigenen Zahlengenerator. java.util.Random Die Klasse Random besitzt zwei Konstruktoren. ■
■
Random(int seed) erzeugt ein Zahlengenerator-Objekt, das mit einem int-Wert initialisiert wird. Das Besondere daran: zwei Random-Objekte, die mit dem gleichen seed-Wert initialisiert wurden, produzieren stets die gleiche Folge von Zufallszahlen. Dies bedeutet aber auch, dass ein Programm, das ein Random()-Objekt mit einem seed-Wert verwendet, immer die gleiche Folge von Zufallszahlen verarbeitet. Diese zufälligen, aber reproduzierbaren Zahlenfolgen sind zum Testen und Vergleichen von Programmen, die mit Zufallszahlen arbeiten, sehr wichtig. Random() erzeugt ein Zahlengenerator-Objekt, das intern mit der aktuellen Systemzeit initialisiert wird und daher nicht reproduzierbare Zufallszahlen liefert. java.util.Random generator = new Random(3); reproduzierbare Zahlen java.util.Random generator = new Random(); nicht reproduzierbare Zahlen
TIPP
Die Praxis sieht in der Regel so aus, dass man während der Entwicklungsphase den Zahlengenerator mit einem festen seed-Wert initialisiert. Dies erleichtert das Testen und Debuggen des Programms. Zum Schluss, wenn das Programm fertig gestellt ist und ausgeliefert werden kann, löscht man den seed-Wert. Die eigentlichen Zufallszahlen »zieht« man danach durch Aufruf der RandomMethoden: ■
■
166
nextInt(), nextInt(int n), nextLong() zur Generierung einer ganzzahligen Zufallszahl im jeweiligen Wertebereich (also von int oder long). Die Variante mit dem Parameter n erzeugt eine Zufallszahl zwischen 0 (inklusive) und n (exklusive). nextFloat() und nextDouble() zur Erzeugung einer Gleitkommazahl aus dem Wertebereich von float bzw. double.
Operationen auf numerischen Datentypen
Grundsätzlich ist jede Zahl aus dem Wertebereich gleich wahrscheinlich. Für manche (eher mathematisch-statistische) Anwendungen werden jedoch Folgen von gaußverteilten Zufallszahlen benötigt. Dies erlaubt die Methode nextGaussian(). Angepasste Zufallszahlen Mit Zufallszahlen wie 0.34 oder 0.00032 lässt sich in einem Programm aber nur selten etwas Vernünftiges anfangen. Meist benötigt man ganzzahlige Zufallszahlen aus einem bestimmten Wertebereich, beispielsweise eine zufällig ausgewählte Zahl zwischen 1 und 10 oder 12 und 15. In so einem Fall gehen Sie wie folgt vor.
1
1.
3
2
Zuerst ermitteln Sie, wie viele ganzzahlige Werte der angestrebte Wertebereich umfasst. Wenn Sie beispielsweise Zufallszahlen zwischen 2 und 7 (2 und 7 mit eingeschlossen) benötigen, umfasst der Wertebereich 6 Werte.
2.
4
Dann rufen Sie die Methode Random.nextInt() mit dem in Schritt 1 ermittelten Wertebereich auf (in unserem Beispiel also 6 oder allgemein formuliert MAX – MIN + 1).
5
zufallszahl = generator.nextInt(7 - 2 + 1);
6
In zufallszahl steht jetzt eine Zahl aus dem Bereich 0 bis 5. 3. Zuletzt addieren Sie die Zahl, mit der der Wertebereich beginnen soll (im Beispiel die 2).
7
zufallszahl = generator.nextInt(7 - 2 + 1) + 2;
In zufallszahl steht jetzt eine Zahl zwischen 2 und 7.
8
Das folgende Programm erzeugt eine Zufallszahl zwischen den inklusiven Grenzen MIN und MAX:
9
Listing 6.1: Zufallszahl.java – Zufallszahlen erzeugen // Einsatz des Zufallszahlengenerators import java.util.*;
10
class Zufallszahl { public static void main(String args[]) { final int MIN = 5; final int MAX = 10; int zz;
11
12
Random generator = new Random();
13
zz = generator.nextInt(MAX-MIN + 1) + MIN; System.out.println(" Gezogene Zahl: " + zz);
14
} }
167
Operatoren und Ausdrücke
6.4
Operationen auf boolean
Neben den allgemeinen Operatoren (siehe Abschnitt »Ausdrücke«) gibt es für die Verarbeitung boolescher Werte noch eine Klasse eigener Operatoren: die logischen Operatoren.
6.4.1
Die logischen Operatoren
Die logischen Operatoren kombinieren boolesche Ausdrücke (also Ausdrücke, die einen bestimmten Wahrheitswert darstellen – etwa Vergleiche) zu komplexeren Aussagen, die ebenfalls wieder wahr (true) oder falsch (false) sein können. Tabelle 6.9: Die logischen Operatoren
Operator
Aufgabe
Beispiel
&&, &
Logisches Und
(i > 1 && i < 10)
||, |
Logisches Oder
(i < 1 || i > 10)
!
Logisches Nicht
!(i == 0)
Ebenso wie die Vergleichsoperatoren werden die logischen Operatoren meist in Bedingungen von Schleifen und Verzweigungen eingesetzt und daher in Kapitel 7.1 näher vorgestellt.
6.5
Operationen auf Strings
Die Strings nehmen unter den Datentypen Javas eine Sonderstellung ein, da sie einerseits zu den Referenztypen zählen und durch Objekte der Klasse String repräsentiert werden, andererseits aber so stark in die Sprache integriert sind, dass sie fast schon wie Daten elementarer Datentypen verwendet werden können (siehe hierzu Kapitel 5.6). Zu diesen besonderen Vorzügen der Strings gehört auch, dass sie als einziger Referenztyp über einen eigenen Operator verfügen: den Konkatenationsoperator.
6.5.1
Der Konkatenationsoperator
Mit die wichtigste Operation zur Bearbeitung von Strings ist das Anhängen eines Strings an einen anderen. Im Programmierjargon bezeichnet man dies als Konkatenation. Um das Aneinanderhängen von Strings so bequem wie möglich zu machen, wurde in Java der +-Operator auch für Strings definiert. String gruss = "Hallo"; String name = "Programmierer";
168
Operationen auf Strings
String ausgabe; ausgabe = gruss + " " + name + "!"; System.out.println(ausgabe);
Verwechseln Sie den Konkatenationsoperator nicht mit dem Additionsoperator für numerische Datentypen. Beide haben zwar das gleiche Symbol (+), repräsentieren aber gänzlich unterschiedliche Operationen.
1 HALT
2
Steht das +-Zeichen zwischen zwei Operanden, von denen einer dem String-Typ, der andere einem numerischen Typ angehört, wandelt der Compiler den numerischen Wert automatisch in einen String um, und interpretiert das + als Konkatenationsoperator:
3
int zahl = 144; int wurzel;
4
wurzel = Math.sqrt(zahl); System.out.println("wurzel : " + wurzel);
5
6.5.2 Die Klasse String
6
Sieht man einmal von den allgemeinen Operatoren =, ==, != und (typ) ab, ist der Konkatenationsoperator der einzige Operator, der für Strings definiert ist. Kompensiert wird dies durch die zahlreichen Methoden der Klasse String, die Ihnen jeden erdenklichen Komfort bei der Bearbeitung Ihrer Zeichenketten und Texte bietet.
7 8
Konstruktoren Insgesamt definiert die Klasse String neun Konstruktoren, darunter: ■
9
String() Erzeugt ein leeres String-Objekt, das den leeren String "" repräsentiert.
10
String str = new String(); ■ ■
■ ■
String(byte[] bytes) String(bytes[] bytes, String charset) Diese beiden Konstruktoren erwarten ein byte-Array mit den einzelnen Zeichen. Da das String-Objekt intern mit Unicode arbeitet, werden die einzelnen Bytes intern vom Konstruktor in Unicode übersetzt. Der erste Konstruktor wählt dazu die Standardzeichencodierung des zugrunde liegenden Betriebssystems (in Deutschland in der Regel ISO8859-1), der zweite verwendet die in charset angegebene Codierung. String(char[] chars) String(char[] chars, int offset, int count) Diese Konstruktoren initialisieren ihre Objekte mit den Zeichen aus einem char-Array. Da der Datentyp char in Java Unicode-Zeichen spei-
11
12 13 14
169
Operatoren und Ausdrücke
■
chert, entfällt hier die Zeichenkonvertierung. Der zweite Operator initialisiert das String-Objekt mit den count-Zeichen, die ab der Position offset im Array chars zu finden sind. String(String original) Erzeugt ein neues String-Objekt als Kopie eines bestehenden StringObjekts. Anders als beim =-Operator, der Referenzen auf StringObjekte kopiert, wird hier eine echte Kopie erstellt, d.h., es gibt danach zwei String-Objekte, die beide gleich lautende Strings enthalten.
Vergessen Sie auch nicht die Möglichkeit, Strings automatisch erzeugen zu lassen: TIPP
String str = "Ich bin ein String";
Methoden Tabelle 6.10 zeigt eine Auswahl der wichtigsten Methoden von String. Tabelle 6.10: Wichtige Methoden der Klasse String
Methode
Beschreibunga
char charAt(int index)
Liefert das Zeichen an der Position index. Erste Position ist 0.
int compareTo(String str)
Vergleicht den String mit str. Rückgabewert ist kleiner 0 für »lexikografisch kleiner«, 0 für »gleich« und größer 0 für »lexikografisch größer«.
boolean equals(String str)
true, wenn der String identisch ist zu str (entspricht Wert 0 bei compareTo()).
String concat(String str)
Vergrößert den String, indem str angehängt wird.
String replace(char alt, char neu)
Ersetzt alle Vorkommen von alt durch neu.
byte[] getBytes()
Liefert die Zeichenkette des Strings als byte-Array, codiert mit der Standardzeichencodierung.
int int int int
indexOf(int z) indexOf(int z, int start) indexOf(String str) indexOf(String str, int start)
Durchsucht den String nach z bzw. str und liefert die erste Position, wo z gefunden wurde (oder –1 bei Misserfolg). Optional kann die Startposition mitgegeben werden, ab der die Suche beginnen soll.
int length()
Die Anzahl an Unicode-Zeichen der Zeichenkette (nicht die Anzahl an Bytes!)
boolean matches(String reg)
true wenn der String den regulären Ausdruck reg erfüllt.
boolean startsWith(String str) boolean endsWith(String str)
170
true, wenn der String mit str anfängt bzw. endet.
Operationen auf Referenztypen
Methode
Beschreibunga
String substring(int start, int end)
Liefert den Teilstring ab Position start bis Position end (exklusive).
String trim()
Entfernt führende/nachfolgende Leerzeichen.
static String valueOf(xxx wert)
Liefert eine Stringdarstellung von wert vom primitiven Datentyp xxx (z. B. int, float, usw.)
a.
Tabelle 6.10: Wichtige Methoden der Klasse String (Forts.)
1
Positionsangaben sind stets nullbasiert! Bei Bereichsangaben der Art start – ende gilt, dass ende die erste Position bezeichnet, die nicht mehr betroffen ist (z. B. bei substring()).
Das Vergleichen von Strings wird noch einmal im Abschnitt »String-Vergleiche« in Kapitel 7.1 aufgegriffen. Fortgeschrittene und tiefer gehende Aspekte der String-Programmierung werden in Kapitel 28.1 behandelt.
2 3 REF
4
6.6
Operationen auf Referenztypen 5
Für Referenztypen gibt es keine eigenen Operatoren. Immerhin ist es möglich, mithilfe der allgemeinen Operatoren (siehe Abschnitt 6.2) Typumwandlungen zwischen Referenztypen vorzunehmen sowie Referenzen zu kopieren und zu vergleichen:
6 7
EineKlasse obj1 = new EineKlasse(); EineKlasse obj2; obj2 = obj1; // Referenz zuweisen ... if( obj1 != obj2) { // Referenzen vergleichen ...
8 9
6.6.1
Die Basisklasse Object
Alle in Java definierten Klassen und Arrays gehen auf eine oberste Basisklasse zurück: Object aus dem Paket java.lang.
10
Diese Rückführung sämtlicher Klassen und Arrays auf eine Basisklasse ist von großer Bedeutung für die Programmierung, und zwar in zweierlei Hinsicht: ■ ■
11
12
Erstens kann jeder Klassen- oder Arraytyp in den Typ Object umgewandelt werden. Zweitens erbt jede Klasse und jedes Array die in Object definierten Elemente.
13
Die Bedeutung der Typumwandlung in Object lässt sich ohne Hintergrundwissen und Erfahrungen in der objektorientierten Programmierung nur schwer ermessen. Wir werden daher in Kapitel 14.3 noch einmal darauf zurückkommen.
14
171
Operatoren und Ausdrücke
Die Vorzüge, die sich aus der Vererbung der in Object definierten Elemente ergeben, liegen dagegen auf der Hand: Die Lücke, die die fehlenden Operatoren aufreißen, kann durch geeignete Methoden in Object wieder geschlossen werden. So gibt es in Object beispielsweise Methoden, mit denen man die Objekte von Referenztypen kopieren oder vergleichen kann. Warum aber so umständlich? Warum haben die Designer von Java nicht einfach weitere Operatoren definiert: EineKlasse obj1 = new EineKlasse(); EineKlasse obj2; obj2 = obj1; obj2 =$ obj1;
// Referenz aus obj1 in obj2 kopieren // Objekt, auf das obj1 verweist, in obj2 kopieren // !!! erfundene Syntax, in Java nicht gültig !!!
Bedenken Sie, dass jede Klasse (und jedes Array) einen eigenen Datentyp darstellt und eine ganz eigene Art von Objekten erzeugt. Operationen zu implementieren, die für alle diese unterschiedlichen Objekttypen sinnvoll und nützlich sind, ist schlichtweg unmöglich. Oder können Sie sich vorstellen, wie eine Vergleichsoperation aussehen müsste, mit der Zeitangaben (Objekte der Klasse java.util.Date) ebenso verglichen werden können wie Objekte einer von Ihnen definierten Klasse Adresse oder die Objekte irgendeiner beliebigen anderen Klasse? Genau diese Quadratur des Kreises müssten aber in der Sprache verankerte Operatoren vollbringen. Das Konzept der via Object vererbten Methoden ist dagegen wesentlich flexibler, denn jede Klasse hat die Möglichkeit, die Implementierung der geerbten Methoden zu überschreiben. (Wie die Überschreibung genau funktioniert, wird Thema der Kapitel 13 und 15 sein.) So definiert Object beispielsweise eine Methode toString(), die dazu gedacht ist, eine String-Repräsentation des aktuellen Objekts zurückzuliefern. In Object ist diese Methode so implementiert, dass sie einen String erzeugt, der aus dem Klassennamen des Objekts, dem @-Zeichen und einen das Objekt spezifizierenden Integer-Code (Hash-Code) besteht: »klassenname@hashcode«. Object obj = new Object(); System.out.println(obj.toString()); // Ausgabe: java.lang.Object@7ced01
Da alle Klassen von Object abgeleitet werden, können Sie sich darauf verlassen, dass jedes Objekt (egal welcher Java-Klasse) über die Methode toString() verfügt, die eine String-Repräsentation des Objekts zurückliefert. Dies kann die von Object vorgegebene Stringdarstellung sein, es kann aber auch eine von der Klasse des Objekts angepasste Version sein. Denken Sie beispielsweise an die Wrapper-Klassen zu den numerischen Datentypen, die toString() so überschreiben, dass eine Stringdarstellung der durch die Objekte repräsentierten Zahlen zurückgeliefert wird. Integer zahl = new Integer(314); System.out.println(zahl.toString());
172
// Ausgabe: 314
Operationen auf Referenztypen
Die Methoden von Object Die Klasse Object definiert zwei Gruppen von Methoden. Die erste Gruppe dient der Threadsynchronisierung: void void void void void
notify() notifyAll() wait() wait(long timeout) wait(long timeout, int nanos)
1
Diese Methoden sind fertig implementiert und können nicht überschrieben werden! Sie werden in Kapitel 30 behandelt.
2
In der zweiten Gruppe sind die allgemein nützlichen Methoden zusammengefasst, die – abgesehen von getClass() – von abgeleiteten Klassen überschrieben werden können:
3
Methode
Beschreibung
protected Object clone()
Erzeugt eine Kopie des aktuellen Objekts und liefert diese zurück. Die von Object vorgegebene Implementierung löst eine CloneNotSupportedException aus, wenn die Klasse, für deren Objekt die Methode aufgerufen wird, nicht die Cloneable-Schnittstelle implementiert.
4
Tabelle 6.11: Allgemein nützliche Methoden aus Object
5
6
Ansonsten wird eine »flache« Kopie erstellt, d.h., die Inhalte der Felder werden 1:1 kopiert (siehe Kapitel 17).
boolean equals(Object obj)
Siehe Kapitel 17 für ein Beispiel, wie diese Methode überschrieben werden kann.
7
Testet zwei Objekte auf Gleichheit.
8
Die von Object vorgegebene Implementierung liefert true, wenn die Referenzen auf das aktuelle Objekt und das Argument identisch sind – arbeitet also genauso wie der ==-Operator für Referenzen.
9
Siehe Kapitel 17 für ein Beispiel, wie diese Methode überschrieben werden kann.
protected void finalize()
10
Diese Methode wird vom Garbage Collector (Speicherbereinigung) aufgerufen, wenn das Objekt aufgelöst wird. Die von Object vorgegebene Implementierung tut nichts.
11
Abgeleitete Klassen können diese Methode überschreiben, wenn ihre Objekte irgendwelche Systemressourcen freigeben oder sonstige Aufräumarbeiten verrichten möchten.
12
Siehe auch Abschnitt »Objektauflösung« in Kapitel 8.6.
Class getClass()
Liefert die Laufzeitklasse des Objekts zurück.
13
Kann nicht überschrieben werden. Siehe Abschnitt »Typidentifizierung zur Laufzeit (RTTI)« in Kapitel 14.3.
14
173
Operatoren und Ausdrücke
Tabelle 6.11: Allgemein nützliche Methoden aus Object (Forts.)
Methode
Beschreibung
int hashCode()
Liefert einen Hashcode für das Objekt zurück, wie er beispielsweise von Objekten verlangt wird, die als Schlüssel zu Hashtabellen (Instanzen von java.util.Hashtable oder java.util.HashMap) verwendet werden. Die von Object vorgegebene Implementierung liefert einen für jedes Objekt eindeutigen Integer-Wert (meist wird die Methode so implementiert, dass sie die Speicheradresse des Objekts zurückliefert). Siehe Kapitel 29.5 für ein Beispiel zur Verwendung von Hashtabellen.
String toString()
Liefert eine Stringdarstellung des Objekts zurück. Die von Object vorgegebene Implementierung setzt den String aus den Rückgabewerten von getClass() und hashCode() zusammen:
getClass().getName() + "@" + Integer.toHex String(hashCode()); Siehe Kapitel 17.1 für ein Beispiel, wie diese Methode überschrieben werden kann.
Denken Sie nicht nur daran, dass Sie die Object-Methoden für Objekte jeder beliebigen Klasse nutzen und aufrufen können. Denken Sie auch beim Schreiben eigener Klassen daran, die Methoden – wo möglich und sinnvoll – durch eigene, den Klassen gemäße Implementierungen zu überschreiben (siehe Kapitel 17).
6.7
Sonstige Operatoren
Weitere Operatoren von Java sind: Tabelle 6.12: Sonstige Operatoren
Operator
Aufgabe
,
Aneinanderreihung von Variablen oder Ausdrücken, die jeder für sich ausgewertet werden sollen:
int i = 1, j = 3, n = 3; .
Zugriff auf Elemente von Objekten:
obj.element = 3; []
Zugriff auf Elemente eines Arrays (siehe Kapitel 9.2)
{}
Anweisungsblöcke Initialisierungslisten für Arrays (siehe Kapitel 9.1)
174
()
Methodenaufruf und Parameterlisten (siehe Kapitel 8.3)
instanceof
Ermittelt, ob ein Objekt einer bestimmten Klasse angehört (siehe Abschnitt »Typidentifizierung zur Laufzeit (RTTI)« in Kapitel 14.3)
?:
Bedingungsoperator (siehe gleichnamiger Abschnitt in Kapitel 7.2)
Reihenfolge der Ausdrucksauswertung
6.8
Reihenfolge der Ausdrucksauswertung
In Ausdrücken, die aus mehreren Operatoren bestehen, stellt sich die Frage, wie diese Ausdrücke berechnet werden. Dabei spielen die Priorität und die Assoziativität der Operatoren, die Reihenfolge der Operandenauswertung sowie die Klammerung eine entscheidende Rolle. 1
6.8.1
Priorität
Nehmen wir zum Beispiel die Variable y, der wir den Wert des Ausdrucks 3 + x * 4 zuweisen:
2
y = 3 + x * 4;4
3
Die Variable x habe den Wert 2. Welchen Wert hat die Variable y nach der Zuweisung?
4
Im ersten Moment würde man wohl davon ausgehen, dass die Operatoren in der Reihenfolge abgearbeitet werden, in der sie im Ausdruck von links nach rechts auftauchen. Zuerst würde 3 + 2 berechnet, dann das Ergebnis mit 4 multipliziert. Die Variable y erhielte damit den Wert 20.
5
Auf den zweiten Blick wird man sich vielleicht überlegen, dass – wie in der Mathematik – die Punktrechnung vor der Strichrechnung ausgeführt werden könnte. Dann würde zuerst 2 * 4 berechnet und zum Ergebnis der Wert 3 addiert. Die Variable y erhielte dann den Wert 11.
6 7
Tatsächlich ist Letzteres der Fall. Der Grund hierfür ist, dass die Operatoren in Kategorien unterschiedlicher Priorität eingeteilt sind und Operatoren mit höherer Priorität vor den Operatoren niedriger Priorität abgearbeitet werden. Da die Operatoren für Multiplikation und Division in der internen Rangordnung der Operatoren eine höhere Priorität einnehmen als die Operatoren für Addition und Subtraktion, gilt auch bei der Auswertung von Ausdrücken, dass Punkt vor Strich geht.
8 9 10
Tabelle 6.13 am Ende des Abschnitts listet die verschiedenen Operatoren geordnet nach ihrer Priorität auf.
11
6.8.2 Klammerung
12
Wenn Sie sich wegen der Auswertungsreihenfolge der Operatoren nicht sicher sind oder einfach eine andere Auswertungsreihenfolge vorgeben wollen, können Sie die Ausdrücke klammern. Die Klammern haben oberste Priorität, das heißt, Teilausdrücke in Klammern werden vorrangig ausgewertet.
13 14
4
Mathematiker werden hierin unschwer die Gleichung einer Geraden mit der Steigung 4 erkennen, doch dies sei nur so nebenbei bemerkt.
175
Operatoren und Ausdrücke
Wenn Sie also möchten, dass der Interpreter in dem Ausdruck 3 + x * 4 zuerst die 3 zu dem Wert von x addiert und dann das Ergebnis mit 4 multipliziert und an y zuweist, müssen Sie schreiben: y = (3 + x) * 4;
Aber auch wenn die Operatoren in der gewünschten Reihenfolge ausgewertet werden, können Klammern hilfreich sein, da sie die Lesbarkeit des Codes verbessern. y = 3 + (x * 4);
HALT
Überflüssige Klammern entlarven Sie nicht gleich als Dilettanten, der die Regeln für die Operatorenauswertung nicht beherrscht, sondern zeugen vielmehr von Verantwortungsbewusstsein und dem Bemühen um gut verständliche Quelltexte.
6.8.3 Operandenauswertung Weiter oben wurde ausgeführt, dass Operatoren mit höherer Priorität vor anderen Operatoren im Ausdruck mit niedriger Priorität ausgeführt werden. Man könnte daraus schließen, dass der Compiler bei der Ausdrucksauswertung sich den Operator höchster Priorität sucht, diesen auswertet und dann zu den Operatoren niedriger Priorität übergeht. Doch dem ist nicht so! Der Compiler arbeitet die Ausdrücke grundsätzlich von links nach rechts ab, wobei er sofort mit der Auswertung der Operanden beginnt. Stellt er fest, dass er die Operanden zu einem Operator vollständig ausgewertet hat, führt er die Operation durch. Und wo kommt dabei die Priorität ins Spiel? Sie bestimmt die Zuordnung der Operanden zu den Operatoren. Ein Beispiel soll dies verdeutlichen. x = 3; y = x - 7 * ++x + x;
Würde der Compiler hier mit der Auswertung bei dem Operator mit der höchsten Priorität beginnen (++x), würde x als Erstes auf 4 gesetzt und der letztendlich ausgewertete Ausdruck wäre 4 – (7 * 4) + 4 = -20. Tatsächlich ermittelt der Compiler aber zuerst die Speicheradresse von y, dann springt er zur rechten Seite der Zuweisung und ersetzt x durch den aktuellen Wert 3. Er könnte jetzt die 3 an y zuweisen, aber er erkennt die höhere Priorität des Minus-Operators, die ihm sagt, dass die 3 eben nicht der zweite Operand des Zuweisungsoperators ist, sondern der erste Operand des Minus-Operators. Also sucht er den zweiten Operanden des MinusOperators und findet die 7, die aber wieder nur der erste Operand des Multiplikationsoperators und so weiter. Schließlich erreicht er den Inkrementoperator und sein Argument x: y = (3 - (7 * (++x)
176
Reihenfolge der Ausdrucksauswertung
Da der Inkrementoperator ++ nur einen einzigen Operanden besitzt (x), führt er die Inkrementoperation aus: x wird der Wert 4 zugewiesen und dieser Wert ergibt auch gleichzeitig den zweiten Operanden des Multiplikationsoperators. y = (3 - (7 * 4)
Jetzt kann der Compiler auch die Multiplikation ausführen.
1
y = (3 - 28
Und auch die Subtraktion wird berechnet, da der nachfolgende Operator keine höhere Priorität hat.
2
y = (-25
3
Das Zwischenergebnis -25 ist der erste Operand des noch verbleibenden +-Operators, dessen zweiter Operand der Wert von x (jetzt 4) ist. y = (-25 + 4);
4
Der Compiler muss also nur noch die Addition durchführen und das Ergebnis ist schließlich der rechte Operand für die Zuweisung an y.
5
Beachten Sie, dass der Compiler, obwohl die Ausdrucksauswertung von links nach rechts voranschreitet, die beteiligten Operatoren strikt in der Reihenfolge ihrer Priorität ausgewertet hat.
6 TIPP
7 Der hier geschilderte Ablauf beschreibt die Vorgehensweise des javac-Compilers, ist allerdings nicht durch den Java-Standard abgesichert. Dieser schreibt nämlich lediglich vor, dass die Priorität berücksichtigt wird, dass ein Operator erst nach seinen Operanden ausgewertet wird und dass für binäre Operatoren immer zuerst der linke Operand berechnet wird. Ausdrücke mit Nebeneffekten (siehe unten), könnten daher von verschiedenen Compilern unterschiedlich berechnet werden.
HALT
8 9 10
6.8.4 Prioritätentabelle der Operatoren Die folgende Tabelle listet die Operatoren nach ihrer Priorität geordnet auf. 1 ist die höchste Priorität:
11
12 13 14
177
Operatoren und Ausdrücke
Tabelle 6.13: Priorität und Assoziativität der Operatoren
Priorität
Operatoren
Bedeutung
1
() [] .
Methodenaufruf
++ –– +, – ~ ! ()
Inkrement
2
Arrayindex Elementzugriff
Dekrement Vorzeichen Bitkomplement logische Negation Typumwandlung
3
* / %
Multiplikation
+ – +
Addition
> >>>
Linksverschiebung
= instanceof
kleiner, kleiner gleich
== !=
gleich
& &
bitweises UND
^ ^
bitweises XOR
| |
bitweises ODER
11
&&
logisches UND
12
||
logisches ODER
13
?:
Bedingungsoperator
14
= *=, /=, %=, +=, –=, &=, ^=, |=, =, >>>=
Zuweisung
,
Komma-Operator
4
5
6
7
8
9
10
15
178
Division Modulo (Rest der Division)
Subtraktion Konkatenation (Stringverkettung)
Rechtsverschiebung Rechtsverschiebung
größer, größer gleich Typüberprüfung eines Objekts
ungleich
logisches UND
logisches XOR
logisches ODER
zusammengesetzte Zuweisung
Nebeneffekte
6.9
Nebeneffekte
Beim Aufbau komplexerer Ausdrücke, die mehrere Operatoren und Werte enthalten, sollte man unbedingt auf mögliche unerwartete Nebeneffekte achten. Nebeneffekte ergeben sich immer dann, wenn eine Anweisung implizit weitere Anweisungen auslöst – beispielsweise wenn Sie in den Ausdruck auf der rechten Seite einer Anweisung einen Methodenaufruf einbauen oder einen Operanden inkrementieren oder dekrementieren.
1 2
y = Math.sin(2.4) * 3; y = 3 * ++x;
3
Meist sind solche Nebeneffekte erwünscht und werden gezielt eingesetzt. Ungenaue Kenntnisse der Arbeitsweise des Compilers können aber ebenso wie »besonders geschickt« ausgetüftelte Ausdrücke schnell dazu führen, dass sich Nebeneffekte einstellen, die vom Programmierer weder gewünscht noch vorhergesehen wurden.
4 5
Die meisten ungewollten Ergebnisse resultieren daraus, dass ■
■
Nebeneffekte auf die im Ausdruck enthaltenen Werte rückwirken und nicht in der vom Programmierer erwarteten Reihenfolge abgearbeitet werden. (Hierfür konnten Sie in dem vorangehenden Abschnitt 6.8 bereits eine Reihe von Beispielen sehen.) ein (Teil) Ausdruck gar nicht ausgeführt wird. Dies ist meist der Fall, wenn die logischen Vergleichsoperatoren &&, || eingesetzt werden, bei denen die Auswertung von links beginnend direkt abgebrochen wird, wenn der Wert des gesamten Ausdrucks bereits feststeht, z. B.
6 7 8
if(a++ == 4 && b++ == 4) { // ... }
9
Hier möchte der Programmierer wahrscheinlich, dass a und b beide um eins erhöht werden und hat dies in die if-Abfrage integriert. Während der Ausführung wird dies aber nur dann passieren, wenn a den Wert 4 hat. Falls a einen anderen Wert hat, wird der Ausdruck b++ == 4 gar nicht mehr erreicht, sodass nur a inkrementiert wird.
10 11
12 13 14
179
Inhalt
7
Kontrollstrukturen 1 2
Bis jetzt sahen die vorgestellten Beispiele und Codefragmente stets so aus, dass die Anweisungen in der main()-Methode nacheinander von oben nach unten ausgeführt wurden.
3
Mithilfe spezieller Sprachkonstrukte kann der Programmierer diesen sequenziellen Programmfluss aufbrechen und umleiten. Die Programmlogik unterscheidet hierbei zwischen:
4
Verzweigungen. Anhand einer im Programm definierten Bedingung wird entschieden, an welcher Stelle das Programm weiter ausgeführt werden soll. Schleifen. Ein bestimmter Anweisungsblock wird wieder und wieder ausgeführt, bis eine im Programm definierte Abbruchbedingung erfüllt ist. Sprüngen. Die Programmausführung wird von der aktuellen Anweisung zu einer bestimmten anderen Anweisung umdirigiert.
■
■
■
5 6
7
Die folgenden Abschnitte beschäftigen sich mit der Syntax dieser Sprachkonstrukte, ihrem praktischen Einsatz in Programmen sowie den potenziellen Fallen und Fehlerquellen, die zu beachten sind.
8 9
Neben den Verzweigungen, Schleifen und Sprüngen gibt es noch ein weiteres Konzept zur Umlenkung des Programmflusses: die Exceptions. Exceptions dienen ausschließlich der Fehler- und Ausnahmebehandlung. Sie werden in Kapitel 16 behandelt.
INFO
10 11
7.1
Entscheidungen und Bedingungen 12
Zweimal schon hat die Computertechnologie entscheidende Umwälzungen in unserem alltäglichen Leben und im Gefüge unserer modernen Gesellschaften herbeigeführt. Die erste Umwälzung begann in den Siebzigern mit der Miniaturisierung der elektronischen Rechner1 und der Einführung des Personal Computers, kurz PC. Immer kleiner und immer leistungsfähiger
13 14
Bereits 1947 hatten Shockley, Bardeen und Brattain den Transistor erfunden, der in den Fünfziger Jahren nach und nach die sperrigen Vakuumröhren ersetzte. Die Winzigkeit des Transistors hatte aber auch Nachteile: in komplexeren, dichten Schaltungen war er nur sehr schwer zu verlöten. Dieses Problem wurde Ende der Fünfziger durch die integrierten Schaltkreise (zeitgleich erfunden von Jack Kilby und Robert N. Noyce) gelöst.
181
Index
1
Kontrollstrukturen
werdend breitete sich der PC epidemisch aus und revolutionierte die Datenverarbeitung in Wissenschaft und Industrie, in Amtsstuben wie in Kinderzimmern. Die zweite Revolution folgte in den Neunzigern und war eine der wenigen, vielleicht sogar die einzige Revolution, die vom Militär initiiert wurde – wenn auch gänzlich unbeabsichtigt. Die Rede ist vom Internet, das aus dem vom U.S.-Militär geförderten ARPANET hervorging2 und sich – trotz gelegentlicher Blasenbildung und zunehmender Kommerzialisierung – zu einem weltumspannenden, internationalen Forum entwickelt hat. Wird es nach PC und Internet noch eine weitere Computerrevolution geben? Die Chancen stehen gut, dass die Entwicklungen auf dem Gebiet der virtuellen Realität unsere Realität genauso entscheidend verändern werden wie zuvor der PC und das Internet. Und auch die künstliche Intelligenz – anfangs stark gefördert, später mangels ausbleibender Resultate zum Teil belächelt – schickt sich so langsam an, immer mehr Entwicklungen in marktreife Produkte zu überführen – was wohl auch daran liegt, dass die Ansprüche an die »Intelligenz« der eigenen Produkte zurückgeschraubt wurden. Für uns ist es hingegen an der Zeit, die »Intelligenz« unserer Programme zu erhöhen. Was ist überhaupt Intelligenz? Intelligenz ist schwer zu definieren und noch schwerer zu messen3. Es gibt allerdings bestimmte Eigenschaften, die eng mit der Vorstellung von Intelligenz verbunden sind: beispielsweise die Lernfähigkeit oder die Möglichkeit auf wechselnde Umwelteinflüsse zu reagieren. Begreift man nun die Daten, die ein Programm verarbeiten soll, als die »Umwelteinflüsse« des Programms, zeichnet sich der Weg, wie Programme intelligenter werden, klar ab: sie müssen so programmiert werden, dass sie in Abhängigkeit von den Werten der Daten unterschiedlich reagieren können. Ein Programm, das eine Zahl einliest und die Wurzel dieser Zahl berechnet, ist in diesem Sinne ein dummes Programm. Gibt man eine negative Zahl ein (für die die Wurzel nicht definiert ist), liefert das Programm falsche Ergebnisse oder stürzt ab. Ein besseres, intelligenteres Programm prüft die Eingabe und entscheidet dann, ob es die Wurzel berechnet und ausgibt oder ob es den Benutzer mit einer passenden Meldung auf seinen Bedienungsfehler hinweist.
2
3
182
In den Sechzigerjahren galt eine der Hauptsorgen des U.S.-Militärs seiner fragilen Infra- und Kommunikationsstruktur, die im Falle eines atomaren Erstschlags seitens der UdSSR vollkommen zusammenbrechen könnte. Um sich gegen alle Eventualitäten abzusichern, wurde im Auftrag des Militärs an den Universitäten des Landes der Prototyp eines verteilten, dezentralisierten Computer-Netzwerks aufgebaut: das ARPANET. 1983 wurden die militärischen Rechner abgespalten (MILNET), das ARPANET wurde unter Kontrolle der National Science Foundation zum NSFNet und später zum weltumspannenden Internet. Früher versuchte man zum Beispiel, die Intelligenz in Korrelation zum Gehirnvolumen zu bringen. Das meines Wissens schwerste je gemessene menschliche Gehirn gehörte Lord Byron, dem Vater der späteren Lady Ada Lovelace, die bereits im 19. Jahrhundert erste Entwürfe einer Programmiersprache ausarbeitete und heute allgemein als »erste Programmiererin« anerkannt ist. Interessant ist auch, dass das Gehirn von Marilyn Monroe mit 1440 Gramm ein gutes Stück über dem Durchschnitt eines männlichen Gehirns lag.
Entscheidungen und Bedingungen
7.1.1
Bedingungen
Um Entscheidungen treffen zu können, muss ein Programm Kriterien enthalten, auf deren Basis die Entscheidungen gefällt werden. Diese Kriterien oder Bedingungen schreibt der Programmierer im Quelltext als boolesche Ausdrücke nieder. 1
Ein boolescher Ausdruck ist ein Ausdruck, der zu einem booleschen Wert ausgewertet wird. INFO
2
Die einfachste Entscheidung, die ein Programm treffen kann, ist die Frage, ob ein Anweisungsblock ausgeführt werden soll oder nicht. In Java sieht die zugehörige Syntax wie folgt aus:
3
... if (Bedingung) { Anweisungen; } ...
4 5
Diese Konstruktion kann wie ein deutscher Konditionalsatz gelesen werden: »Wenn die Bedingung erfüllt ist, dann (und nur dann) führe den nachfolgenden Anweisungsblock aus.«
6
Als Bedingung kann wie gesagt jeder beliebige Ausdruck dienen, der zur Laufzeit zu einem booleschen Wert ausgewertet werden kann. Ergibt der Ausdruck true, bedeutet dies, dass die Bedingung erfüllt ist und der nachfolgende Anweisungsblock ausgeführt wird. Ergibt der Ausdruck false, ist die Bedingung nicht erfüllt und der Anweisungsblock wird übersprungen.
7 8
Meist bestehen Bedingungen aus Vergleichen, die mithilfe der Vergleichsoperatoren (siehe nachfolgender Abschnitt) aufgesetzt wurden.
9
if (variable >= 100) { System.out.println("variable hat einen Wert größer 100"); }
10
Sie können aber auch einzelne boolesche Werte (in Form von Konstanten, Variablen oder Rückgabewerten von Methoden) als Bedingung verwenden.
11
boolean geaendert; ... if(geaendert) { // Code zum Speichern der Änderungen }
12 13
Schließlich können Sie boolesche Ausdrücke mithilfe der logischen Operatoren (siehe übernächster Abschnitt) zu komplexeren Bedingungen zusammenfassen.
14
183
Kontrollstrukturen
7.1.2
Die Vergleichsoperatoren
Mit den vergleichenden Operatoren, die auch relationale Operatoren genannt werden, wird ein Vergleich zwischen zwei Operanden, die auch Ausdrücke sein können, durchgeführt. Das Ergebnis dieser Operation ist vom Datentyp boolean und somit entweder wahr (true) oder falsch (false). Tabelle 7.1: Vergleichsoperatoren
Operator
Vergleich
Beispiel für i = 2
==
Gleichheit
if (i == 1)
// false
!=
Ungleichheit
if (i != 1)
// true
<
Kleiner als
if (i < 1)
// false
1)
// true
>=
Größer gleich
if (i >= 1)
// true
Das Ergebnis eines Ausdrucks können Sie ■
entweder in einer Variablen vom Typ boolean speichern int var1 = 12; int var2 = 13; boolean b; b = var1 < var2; // b gleich true b = var1 > var2; // b gleich false
■
oder direkt in den Bedingungen von Verzweigungen oder Schleifen verwenden. if (var1 == var2) { System.out.println("gleich"); }
Die Vergleichsoperatoren = sind ausschließlich für die numerischen Datentypen (char, byte, short int, long, float und double) definiert. Operanden anderer Typen werden vom Compiler zurückgewiesen. Gehören die beiden Operanden eines Vergleichsoperators unterschiedlichen numerischen Typen an, werden die entsprechenden numerischen Promotionen durchgeführt (siehe Kapitel 6.1). Die Gleichheitsoperatoren = und != sind für alle Datentypen definiert, also beispielsweise auch für boolean oder für Referenztypen. Im letzteren Fall prüfen die Operatoren, ob die Referenzen auf das gleiche Objekt verweisen. Für »echte«, typspezifische Vergleiche stellen die Referenztypen die Methode equals() zur Verfügung (die in selbst definierten Klassentypen überschrieben werden sollte, siehe Kapitel 17.3).
184
Entscheidungen und Bedingungen
7.1.3
String-Vergleiche
Mancher Programmierer, insbesondere Umsteiger von C++, werden bedauern, dass die Vergleichsoperatoren nicht auch für Strings definiert sind. In C++ ist dies allerdings nur deswegen der Fall, weil die String-Klasse aus der STL-Bibliothek die entsprechenden Operatoren überlädt. Da Java aber keine Operatorenüberladung erlaubt, müssen wir in Java bei Stringvergleichen auf die Vergleichsoperatoren verzichten.
1
Dafür entschädigt uns die String-Implementierung von Java mit ■
2
direkten Vergleichen mit == und != für String-Literal-Instanzen und allen »interned« String-Objekten, siehe Kapitel 5.6. 3
String str1 = "ABCDEFG"; String str2 = new String("ABCDEFG"); str2 = str2.intern();
4 System.out.println(str1 == "ABCDEFG"); System.out.println(str1 == str2); ■
// liefert true // liefert true
5
verschiedenen in der Klasse String definierten Vergleichsmethoden.
boolean isEmpty() Prüft, ob ein leerer String ("") vorliegt. Falls ja, liefert die Methode true zurück.
Neu in Java 6
6
if (s1.isEmpty()) System.out.println(" Leerer String s1");
7
boolean equals(Object obj)
8
Vergleicht den aktuellen String mit obj und liefert true zurück, wenn obj ein String-Objekt ist, das die gleiche Zeichenfolge enthält wie das aktuelle Objekt.
9
String s1 = new String("Genau"); String s2 = new String("Genau"); String s3 = new String("genau"); System.out.println( s1.equals(s2) ); System.out.println( s1.equals(s3) );
10 // liefert true // liefert false
11
12
Die Methode überschreibt die gleichnamige, von Object geerbte Methode (und definiert daher einen Object-Parameter). TIPP
13
boolean equalsIgnoreCase(String str) Vergleicht den aktuellen String mit str und liefert true zurück, wenn beide die gleichen Zeichenfolgen enthalten – wobei nicht zwischen Groß- und Kleinschreibung der Buchstaben unterschieden wird.
14
String s1 = new String("Genau"); String s2 = new String("Genua");
185
Kontrollstrukturen
String s3 = new String("genau"); System.out.println( s1.equalsIgnoreCase(s2) ); System.out.println( s1.equalsIgnoreCase(s3) );
// liefert false // liefert true
boolean contentEquals(StringBuffer str) Vergleicht den aktuellen String mit str und liefert true zurück, wenn beide die gleichen Zeichenfolgen enthalten. String s1 = new String("Genau"); StringBuffer sb = new StringBuffer("Genau"); System.out.println(s1.contentEquals(sb));
// liefert true
int compareTo(Object obj) int compareTo(String str) int compareToIgnoreCase (String str) Diese drei Methoden vergleichen den aktuellen String mit dem String des Arguments und stellen fest, ob der aktuelle String kleiner, gleich oder größer ist (wobei compareToIgnoreCase() nicht zwischen der Groß- und Kleinschreibung von Buchstaben unterscheidet). Das Ergebnis wird als IntegerWert zurückgeliefert. Tabelle 7.2: Rückgabewerte der compareToMethoden
Rückgabewert
Bedeutung
0
aktueller String größer
String s1 = new String("Genau"); String s2 = new String("Genua"); int erg = s1.compareTo(s2); if(erg == 0) System.out.println("gleich"); else if (erg < 0) System.out.println("kleiner"); else System.out.println("groesser");
Ausgabe kleiner
186
Entscheidungen und Bedingungen
Strings vergleichen Strings werden lexikografisch verglichen, das heißt, der Compiler geht die beiden Strings von vorne nach hinten durch und vergleicht Zeichen für Zeichen, bis er auf das erste Zeichen trifft, das in beiden Strings unterschiedlich ist. Dann stellt er fest, welches dieser Zeichen im Unicode zuerst kommt (sprich den kleineren Integer-Code hat). Der String, zu dem dieses Zeichen gehört, ist der lexikografisch kleinere der beiden verglichenen Strings.
1
Sind die beiden Strings bis zum Ende eines Strings gleich, entscheidet die String-Länge. Ist auch diese gleich, sind die beiden Strings gleich.
2
Dass Strings gemäß der Unicode-Codierung ihrer Zeichen verglichen werden, führt dazu, dass die Vergleiche gelegentlich zu »unerwarteten« Ergebnissen führen:
3
–
Großbuchstaben sind bei Stringvergleichen immer kleiner als Kleinbuchstaben (Z ist also kleiner als a), weil die Großbuchstaben im UnicodeZeichensatz vor den Kleinbuchstaben stehen.
4 5
String s1 = new String("Genau"); String s2 = new String("genau"); System.out.println( s1.compareTo(s2) );
–
6
// Ausgabe: -32
Deutsche Umlaute und Sonderzeichen anderer Sprachen sind nicht entsprechend der nationalen Alphabete eingereiht, sondern folgen im Unicode hinter den ASCII-7-Zeichen.
7
Beispielsweise liegt das deutsche 'ä' 127 Stellen hinter dem 'e', weswegen »Fälle« größer als »Felle« gilt:
8
String s1 = new String("Fälle"); String s2 = new String("Felle");
9
System.out.println( s3.compareTo(s4) );
// Ausgabe: 127
10
Wenn Sie Strings gemäß der jeweiligen nationalen Alphabete vergleichen wollen, müssen Sie dazu ein den nationalen Eigenheiten angepasstes Collator-Objekt erzeugen und dessen compare()-Methode aufrufen (die als Ergebnis –1, 0 oder 1 zurückliefert):
11
String s3 = new String("Fälle"); String s4 = new String("Felle");
12
java.text.Collator deCollator = java.text.Collator.getInstance(java.util.Locale.GERMAN);
13
System.out.println( deCollator.compare(s3, s4) ); // -1
14 Wie zu vermuten, liefern die compareTo-Methoden nicht irgendeinen negativen oder positiven Wert zurück, wenn sie unterschiedliche Strings verglei-
187
Kontrollstrukturen
chen. Tatsächlich geben sie die Differenz zwischen den beiden UnicodeCodes des ersten Zeichenpaars, dessen Zeichen nicht übereinstimmen. String s1 = new String("Genau"); String s2 = new String("Genua"); int erg = s1.compareTo(s2);
Hier ist das erste Zeichenpaar, in dem sich die Strings unterscheiden, das vierte Paar ('a' – 'u'). Der Unicode von 'a' ist 0061 (dezimal 97), der von 'u' ist 0075 (dezimal 117). Die Differenz zwischen 'a' und 'u' beträgt folglich –20 und genau diesen Wert liefert die Methode im obigen Beispiel zurück.
7.1.4
Die logischen Operatoren
Java kennt sechs logische Operatoren zur Durchführung logischer UND-, ODER- und NICHT-Verknüpfungen. Bei den logischen UND- und ODEROperatoren wird der Wahrheitswert der Operanden verknüpft, der logische NICHT-Operator konvertiert den logischen Wert seines Operanden ins Gegenteil. Die Operanden müssen selbst boolesche Werte oder Ausdrücke sein. Das Ergebnis einer logischen Verknüpfung ist wiederum ein boolescher Wahrheitswert. Tabelle 7.3: Die logischen Operatoren
Operator
Aufgabe
Beispiel
&&, &
Logisches Und
if (i > 1 && i < 10)
||, |
Logisches Oder
if (i < 1 || i > 10)
^
Logisches XOR (eXklusives Oder)
if (i < 1 ^ i > 10)
!
Logisches Nicht
if ! (i == 0)
&&, & – logisches UND Mit dem Operator && wird eine logische UND-Verknüpfung durchgeführt. Das Ergebnis der Verknüpfung hat nur dann den Wert true, wenn beide Operanden den Wert true besitzen. Die folgende Wahrheitstabelle zeigt Ihnen die möglichen Kombinationen. Tabelle 7.4: UND-Verknüpfung
188
1. Operand
2. Operand
Ergebnis
wahr
wahr
wahr
wahr
falsch
falsch
falsch
wahr
falsch
falsch
falsch
falsch
Entscheidungen und Bedingungen
Mithilfe der logischen UND-Verknüpfung und einer if-Bedingung können Sie beispielsweise überprüfen, ob der Wert einer Variablen zwischen 10 und 100 liegt: if( (x > 10) && (x < 100) )
Der &&-Operator wird von links nach rechts nur so weit ausgewertet, bis das Ergebnis feststeht. Wenn der linke Operand bereits den Wert false liefert, wird die Auswertung abgebrochen.
1
Man kann die Auswertung des zweiten Operanden erzwingen, wenn man den logischen Operator & verwendet:
2
if( (x > 10) & (x < 100) ) // aber ineffizienter
Wegen der optischen Verwechslungsgefahr zum Bitoperator & ist davon jedoch abzuraten. Ferner deutet das Erzwingen einer Gesamtauswertung darauf hin, dass der Code an sich schlecht strukturiert ist, und man sollte überlegen, wie man ihn umschreiben kann (siehe auch Kapitel 6.9).
3
||, | – logisches ODER
5
Der logische ODER-Operator verknüpft seine Operanden so, dass das Ergebnis der Verknüpfung den Wert true hat, wenn mindestens einer der Operanden den Wert true hat.
6
4
Die folgende Wahrheitstabelle zeigt Ihnen die möglichen Kombinationen. 1. Operand
2. Operand
Ergebnis
wahr
wahr
wahr
wahr
falsch
wahr
falsch
wahr
wahr
falsch
falsch
falsch
Tabelle 7.5: ODER-Verknüpfung
7 8 9 10
Mithilfe der logischen ODER-Verknüpfung und einer if-Bedingung können Sie beispielsweise überprüfen, ob der Wert einer Variablen kleiner als 10 oder größer als 100 ist:
11
if( (x < 10) || (x > 100) )
12
Wie der &&-Operator wird auch der ||-Operator nur so weit ausgewertet, bis das Ergebnis feststeht. Wenn Sie möchten, dass immer beide Operanden ausgewertet werden, verwenden Sie den Operator | (Achtung, Nebeneffekte! Vgl. Kapitel 6.9).
13
^ – logisches XOR
14
Der logische XOR-Operator verknüpft seine Operanden so, dass das Ergebnis der Verknüpfung den Wert true hat, wenn genau einer der Operanden den Wert true hat. 189
Kontrollstrukturen
Die folgende Wahrheitstabelle zeigt Ihnen die möglichen Kombinationen. Tabelle 7.6: XOR-Verknüpfung
1. Operand
2. Operand
Ergebnis
wahr
wahr
falsch
wahr
falsch
wahr
falsch
wahr
wahr
falsch
falsch
falsch
! – logisches NICHT Der logische NICHT-Operator verkehrt den Wahrheitswert seines logischen Operanden ins Gegenteil. boolean sechsRichtige = false; if (!sechsRichtige) { System.out.println("Schade: wieder kein Hauptgewinn!"); }
7.2
Verzweigungen
Java kennt drei Formen von Verzweigungen: ■
■ ■
die einfache if-Anweisung, die im Grunde weniger eine Verzweigung als eher eine kleine Umleitung darstellt und lediglich kontrolliert, ob ein nachfolgender Anweisungsblock ausgeführt werden soll oder nicht. die if-else-Verzweigung, die anhand einer Bedingung prüft, welcher von zwei alternativen Anweisungsblöcken ausgeführt werden soll. die switch-Verzweigung, die anhand des Werts eines numerischen Ausdrucks entscheidet, mit welchen von mehreren alternativen Anweisungsblöcken die Programmausführung fortzusetzen ist.
7.2.1
Die einfache if-Anweisung
Die einfache if-Anweisung entscheidet, ob ein nachfolgender Anweisungsblock ausgeführt werden soll oder nicht. if (Bedingung) { Anweisungen; }
Ist die Bedingung erfüllt (true), wird der auf die Bedingung folgende Block ausgeführt, ist die Bedingung falsch (false), wird der Block übersprungen und das Programm wird mit der nächsten Anweisung hinter der if-Anweisung fortgeführt.
190
Verzweigungen
Besteht der von der if-Anweisung kontrollierte Block nur aus einer einzelnen Anweisung, können die Blockklammern weggelassen werden: if (Bedingung) Anweisung;
Niemals hinter die Bedingung ein Semikolon setzen! if (Bedingung); Anweisung;
// !!!! fataler Fehler !!!!!
1 HALT
2
Der Compiler würde sonst annehmen, dass das Semikolon für eine leere Anweisung steht und diese durch die if-Bedingung kontrolliert werden soll (siehe auch Abschnitt »Die leere Anweisung ;« in 7.5). Das folgende Codefragment implementiert einen Zähler, der wie ein zweistelliges, mechanisches Zählwerk bei Erreichen des Werts 100 auf 0 zurückgesetzt wird. Für das korrekte Zurücksetzen sorgt eine if-Anweisung.
3 Beispiel
4
long counter = 0; ... // Zähler inkrementieren ++counter;
5 6
// Zähler zurücksetzen if (counter > 99) counter = 0; ...
Statt der if-Anweisung könnten Sie hier auch den Modulo-Operator zum Zurücksetzen des Zählers verwenden (counter %= 100;). Die if-Anweisung ist allerdings besser verständlich.
7 8 TIPP
9
7.2.2
Die if-else-Verzweigung
Mit der if-Verzweigung können Sie nicht nur die Ausführung einer einzelnen Anweisung oder eines Anweisungsblocks kontrollieren, Sie können auch alternativ einen Anweisungsblock A oder einen Anweisungsblock B ausführen lassen. Dazu wird die if-Konstruktion um eine else-Klausel erweitert:
10
if (Bedingung) { Anweisung(en); } else { Anweisung(en); }
12
Bei Ausführung des Programms wird als Erstes die Bedingung überprüft. Ist diese erfüllt (ergibt der Ausdruck in der Bedingung true), wird der direkt folgende Anweisungsblock ausgeführt. Ist dieser abgearbeitet, wird das Programm direkt mit der nächsten Anweisung unter dem else-Block fortge-
14
11
13
191
Kontrollstrukturen
setzt. Der else-Block selbst wird übersprungen. Ist die Bedingung nicht erfüllt (false), wird der Anweisungsblock direkt unter der if-Bedingung übersprungen und das Programm wird mit dem else-Block fortgesetzt. Abbildung 7.1: Ausführung der if- und der if-else-Anweisung
if (Bedingung) { Anweisung; }
wahr falsch
Beispiel
nächste Anweisung;
if (Bedingung) { Anweisungen; } else { falsch Anweisungen; } nächste Anweisungen; wahr
Die if-else-Verzweigung wird häufig zur Fehlerbehandlung und zur Eigenkontrolle des Programms eingesetzt. Wenn Sie beispielsweise Eingaben vom Anwender einlesen, ist es sinnvoll zu überprüfen, ob diese das richtige Format haben bzw. im gewünschten Wertebereich liegen. Listing 7.1: Wurzel.java – Programm zur Wurzelberechnung import java.util.Scanner; import java.io.Console; public class Wurzel { public static void main(String args[]) { Console cons = System.console(); cons.printf("\n"); double zahl; cons.printf(" Geben Sie eine Zahl ein: "); Scanner sc = new Scanner(cons.reader()); zahl = sc.nextDouble(); if (zahl >= 0.0) cons.printf(" Wurzel von %.2f = %.2f \n", zahl, Math.sqrt(zahl)); else cons.printf(" Wurzel von negativen Zahlen kann nicht " + "berechnet werden \n"); cons.printf("\n"); } }
Ausgabe: Geben Sie eine Zahl ein: 144 Wurzel von 144,00 = 12,00
192
Verzweigungen
Verschachtelung Selbstverständlich ist es auch möglich, if-Verzweigungen zu verschachteln, das heißt, im if- oder else-Teil eine weitere if-Verzweigungen einzubauen. if(n > 100) if(m < 100) System.out.println("m kleiner n\n"); else System.out.println("m groesser oder gleich n ");
1 2
Beachten Sie den Unterschied zu: if((n > 100) && (m < 100)) System.out.println("m kleiner n "); else System.out.println("m groesser oder gleich n ");
Beide Konstrukte sind von der Bedeutung her gleich. Die obere Form lässt sich aber besser ausbauen, wenn n noch mit anderen Werten verglichen werden soll: if(n > 100) { if(m < 100) ... if(p < 100) ... if(r < 100) ... }
3
Die Zuordnung von if und else wird nicht durch die Einrückung bestimmt
5 6
7 8
Beim Verschachteln von if-Verzweigungen stellt sich gelegentlich die Frage, zu welcher if-Bedingung ein gegebener else-Teil gehört (»danglingelse«-Problem). Der else-Teil gehört immer zu dem letzten vorangehenden if-Teil, der noch über keinen else-Teil verfügt. Im obigen Beispiel gehörte der else-Teil also zur if-Bedingung if(m < 100). Gelegentlich sieht man auch die sogenannten »else-if«-Ketten. Diese sind so konstruiert, dass mehrere Bedingungen abgefragt werden, von denen nur eine erfüllt sein kann.
4
9 else if
10 11
int n = -150; if((n > -200) && (n -100) && (n 0) && (n zwei) ? eins : zwei;
Die gleiche Zuweisung kann auch als if-else-Anweisung formuliert werden: if (eins > zwei) max = eins; else max = zwei;
TIPP
Die ?:-Syntax ist zwar sehr kurz und prägnant, sie ist aber auch relativ schwer zu lesen. Setzen Sie sie daher sparsam und nur in Fällen ein, wo sie leicht zu verstehen ist.
7.2.4
Die switch-Verzweigung
Die switch-Verzweigung dient dazu, den Wert einer numerischen Variablen oder eines Ausdrucks mit einer Reihe von Konstanten zu vergleichen. switch (Ausdruck) { case KONST1: Anweisungen; break; case KONST2: Anweisungen; break; default: Anweisungen; break; }
Die switch-Verzweigung wird so ausgewertet, dass zuerst der aktuelle Wert des Ausdrucks bestimmt und dann mit den Konstanten der case-Marken verglichen wird. Im Falle einer Übereinstimmung springt die Programmausführung zu der Anweisung, die auf die entsprechende case-Marke folgt, und wird von dort aus fortgesetzt. Entspricht der Wert des Ausdrucks keiner der
194
Verzweigungen
case-Konstanten, springt die Programmausführung zu der default-Marke. Hier lassen sich also alle Fälle behandeln, für die keine eigenen case-Marken vorgesehen werden.
Die default-Marke ist allerdings optional und kann weggelassen werden. Fehlt die default-Marke und gibt es keine Übereinstimmung zu den caseKonstanten, wird das Programm mit der nächsten Anweisung hinter der switch-Verzweigung fortgeführt.
1
Die break-Anweisung sorgt dafür, dass die switch-Verzweigung nach Abarbeitung der Anweisungen zu einer case-Marke verlassen wird. Wenn für eine case-Marke mehrere Anweisungen ausgeführt werden sollen, müssen diese nicht mit geschweiften Klammern zu einem Anweisungsblock zusammengefasst werden. Der Grund hierfür ist, dass die case-Marken nicht mit den Blöcken einer if-else-Verzweigung zu vergleichen sind. Die gesamte switch-Verzweigung ist im Grunde ein einziger Block von aufeinander folgenden Anweisungen. Die case-Marken legen lediglich fest, ab welcher Anweisung mit der Ausführung dieses Blocks begonnen werden soll. Sie dienen praktisch als Einsprungsmarken. Das Pendant zu den case-Marken sind die break-Anweisungen, die aus der switch-Verzweigung herausführen und mit denen man verhindern kann, dass die Anweisungen der nächsten caseMarke ausgeführt werden.
2 3 TIPP
4 5 6
7
switch-Anweisungen bieten sich immer dann an, wenn eine Variable (oder allgemeiner ein Ausdruck) mehrere Werte annehmen kann und man auf die verschiedenen Werte unterschiedlich reagieren möchte.
8
int note; ... switch(note) { case 1: System.console().println("Note: sehr gut"); break; case 2: System.console().println("Note: gut"); break; case 3: System.console().println("Note: befriedigend"); break; case 4: System.console().println("Note: ausreichend"); break; case 5: System.console().println("Note: mangelhaft"); break; case 6: System.console().println("Note: ungenügend"); break; default: System.console().println("Fehler: ungültige Note"); }
9 10 11
12 13
Auch bei der Auswertung von Menüs für Konsolenanwendungen leisten sie gute Dienste.
14
195
Kontrollstrukturen
Menüs für Konsolenanwendungen Konsolenanwendungen kann man auf einfache Weise mit einem (nicht-grafischen) Menü ausstatten, indem man die Liste der Menübefehle auf dem Bildschirm ausgibt und zur Auswahl der Befehle verschiedene Tasten (Ziffern oder Buchstaben) vorgibt. Drückt der Anwender dann eine der vorgeschlagenen Tasten, wird der Wert der Taste eingelesen, ausgewertet und mit dem Aufruf einer passenden Funktion beantwortet. Die Zuordnung der Tasten zu den aufzurufenden Funktionen erfolgt in einer switch-Anweisung. Listing 7.2: Menue.java – Menüs für Konsolenanwendungen import java.io.*; public class Menue { public static void main(String args[]) { Console cons = System.console(); // Menü anzeigen cons.printf("\n Menü \n\n"); cons.printf(" Neue Adressen eingeben cons.printf(" Nach Adresse suchen cons.printf(" Adressenliste ausgeben cons.printf(" Programm beenden
\n"); \n"); \n"); \n\n");
// Eingabe von Anwender int befehl = 0; String eingabe = cons.readLine(); befehl = Integer.parseInt(eingabe); // Befehl bearbeiten switch(befehl) { case 1: cons.printf(" Neue Adressen eingeben \n"); break; case 2: cons.printf(" Nach Adresse suchen \n"); break; case 3: cons.printf(" Adressenliste ausgeben \n"); break; case 4: cons.printf(" Programm beenden \n"); System.exit(0); break; } cons.printf("\n Auf Wiedersehen! \n"); }
Ausgabe Menü Neue Adressen eingeben Nach Adresse suchen
196
}
Verzweigungen
Adressenliste ausgeben Programm beenden
2 Nach Adresse suchen Auf Wiedersehen!
1 case-Marken ohne break-Anweisung Wie bereits erwähnt, hört die Abarbeitung der switch-Verzweigung nicht automatisch auf, wenn die Anweisungen einer case-Marke abgearbeitet sind. Wenn Sie keine break-Anweisung setzen, wird die Ausführung einfach mit den Anweisungen der nächsten case-Marke fortgesetzt. Meist ist dies unerwünscht, manchmal aber auch goldrichtig. Genauer gesagt immer dann, wenn Sie mehrere Werte des switch-Ausdrucks mit den gleichen Anweisungen verbinden wollen.
2 3 4
Listing 7.3: Taschenrechner.java – Einfacher Taschenrechner
5
import java.io.*; public class Taschenrechner {
6 public static void main(String args[]) throws IOException { Console cons = System.console(); cons.printf("\n");
7
// Nacheinander Operand 1, Operator und Operand 2 einlesen cons.printf(" Ersten Operanden eingeben : "); double zahl1 = Double.parseDouble(cons.readLine());
8
cons.printf(" Operator eingeben : "); char operator = cons.readLine().charAt(0);
9
cons.printf(" Zweiten Operanden eingeben: "); double zahl2 = Double.parseDouble(cons.readLine()); System.out.println();
10 11
cons.printf(" %.2f %c %.2f = ", zahl1, operator, zahl2); // Befehl bearbeiten switch(operator) { case '+': cons.printf(" %.2f \n", (zahl1 + zahl2)); break; case '-': cons.printf(" %.2f \n", (zahl1 - zahl2)); break; case 'X': case 'x': case '*': cons.printf(" %.2f \n", (zahl1 * zahl2)); break; case ':':
12 13 14
197
Kontrollstrukturen
case '/': default:
cons.printf(" %.2f \n", (zahl1 / zahl2)); break; cons.printf("\n Operator nicht bekannt \n"); break;
} cons.printf("\n"); } }
Aufruf: Ersten Operanden eingeben : 12 Operator eingeben : / Zweiten Operanden eingeben: 5 12,0 / 5,0 = 2,4
7.3
Schleifen
Schleifen dienen dazu, eine Anweisung oder einen Anweisungsblock mehrfach hintereinander ausführen zu lassen.
7.3.1
Die while-Schleife
Die while-Schleife ist die allgemeinste Schleife, an deren Aufbau man sich die Grundprinzipien der Funktionsweise von Schleifen gut verdeutlichen kann. Listing 7.4: WhileSchleife.java – berechnet Potenzen von 2 public class WhileSchleife { public static void main(String args[]) { System.out.println(); long n = 0; while (n
34 Falsche ...
35
Formatfehler ... Zu große Zahl ...
36
Die Fakultät ist nicht ...
Achten Sie beim Speichern Ihrer XML-Datei darauf, dass der Text auch wirklich in der Codierung gespeichert wird, die Sie in der XML-Deklaration angezeigt haben (im obigen Fall also UTF-8). Falls Sie z.B. mit dem NotepadEditor von Windows arbeiten, können Sie die Codierung im Speichern unterDialog auswählen. Und denken Sie beim Aufsetzen der Schlüssel/Wert-Paare daran, dass der Inhalt der XML-Elemente buchstabengetreu, inklusive der enthaltenen Zeilenumbrüche und anderer Whitespace-Zeichen, wiedergegeben wird. (Um keine Verwirrung durch satztechnisch bedingte Umbrüche zu stiften, haben wir die Element-Inhalte daher im obigen Beispiel nicht vollständig abgedruckt.)
37 38
HALT
39 40 41
Die ResourceBundle-Klassen Das Laden der XML-Ressourcendatei erfolgt in drei Schritten:
42
Schritt 1: In Ihrer Anwendung laden Sie die Ressourcendatei wie gehabt durch Aufruf von ResourceBundle.getBundle(). Nur dass Sie neben dem
903
Ressourcen und Lokalisierung
Namen der Ressourcendatei auch noch eine Instanz Ihrer (selbst geschriebenen) XMLResourceBundleControl-Klasse übergeben. Listing 32.6: Aus Fakultaet.java – Ressourcen aus einer XML-Ressourcendatei verwenden ... // Ressourcen verfügbar machen try { resources = ResourceBundle.getBundle("ressourcen", new XMLResourceBundleControl()); } catch (MissingResourceException e) { cons.printf(" Ressourcendatei nicht gefunden \n"); System.exit(1); } ...
Schritt 2: Sie leiten Ihre Klasse XMLResourceBundleControl von der Basisklasse java.util.ResourceBundle.Control ab und überschreiben die Methoden getFormats() und newBundle(). Aufgabe dieser Klasse und speziell ihrer newBundle()-Methode ist es: 1.
Die Ressourcendatei zu lokalisieren. Diese Aufgabe ist keineswegs so trivial, wie sie klingt, denn der Lademechanismus für Ressourcen sieht vor, dass nie nur nach einer Datei, sondern nach einer ganzen Familie von Ressourcendateien mit unterschiedlicher Lokale-Spezifität gesucht wird (siehe hierzu Abschnitt 32.2.2). Das Grundprinzip sieht so aus, dass die Methode ResourceBundle.getBundle() (siehe oben) die newBundle()-Methode mehrfach aufruft und ihr dabei verschiedene Kombinationen von Argumenten für den Ressourcendateinamen (wie an getBundle() übergeben), die Lokale (siehe unten) und das Format (wie von der getFormats()-Methode Ihrer ResourceBundle.Control-Klasse zurückgeliefert) übergibt. Ihre getBundle()-Methode muss diese Parameter zu einem vollständigen Dateinamen zusammensetzen und diese zu öffnen versuchen. Was relativ kompliziert klingt, ist in der Praxis allerdings recht schnell erledigt, da Sie letzten Endes nur die Parameter der Methode an geerbte Methoden der Basisklasse übergeben müssen. 2. Einen Stream zur Ressourcendatei zu erstellen. 3. Ein ResourceBundle-Objekt zu erzeugen, das über den Stream die Ressourcendaten einliest. 4. Das ResourceBundle-Objekt zurückzuliefern. Listing 32.7: XMLResourceBundleControl.java – zum Laden von XML-Ressourcendateien import java.io.*; import java.util.*; import java.net.*; public class XMLResourceBundleControl extends ResourceBundle.Control {
904
Ressourcen
public List getFormats(String name) { return Arrays.asList("xml"); } public ResourceBundle newBundle(String name, Locale loc, String format, ClassLoader loader, boolean neuladen) throws IOException, IllegalAccessException, InstantiationException { if ( (name == null) || (loc == null) || (format == null) || (loader == null)) throw new NullPointerException();
29 30 31
ResourceBundle bundle = null; if (format.equals("xml")) {
32
// Schritt 1: Ressourcendatei lokalisieren String bundleName = toBundleName(name, loc); String resName = toResourceName(bundleName, format); URL url = loader.getResource(resName);
33 34
// Schritt 2: Stream zur Ressourcendatei herstellen if (url != null) { URLConnection conn = url.openConnection(); if (conn != null) { if (neuladen) { conn.setUseCaches(false); }
35 36
InputStream stream = conn.getInputStream(); if (stream != null) { // Schritt 3: ResourceBundle-Objekt erzeugen bundle = new XMLResourceBundle(stream); stream.close(); }
37 38
} } }
39
// Schritt 4: ResourceBundle-Objekt zurückliefern return bundle;
40
} ...
41
Geht alles gut, liefert newBundle() ein ResourceBundle-Objekt für Ihre XMLRessourcendatei an getBundle() zurück und getBundle() reicht es weiter an Ihre Anwendung.
42
Was allerdings noch fehlt, ist der Klassentyp für das zurückgelieferte ResourceBundle-Objekt. Im obigen Code (Schritt 3) wurde der Name für
905
Ressourcen und Lokalisierung
diese Klasse schon festgelegt: XMLResourceBundle. Definiert ist die Klasse aber noch nicht. Schritt 3: Sie müssen von ResourceBundle eine eigene Klasse ableiten, die einen Stream auf die Ressourcendatei übernimmt, diesen öffnet, die XMLDaten parst und als Schlüssel/Wert-Paare in einem internen PropertiesObjekt speichert. Hierbei hilft uns die Properties-Methode loadFromXML(): ... // wird als innere Klasse von XMLResourceBundleControl implementiert private static class XMLResourceBundle extends ResourceBundle { private Properties props; XMLResourceBundle(InputStream stream) throws IOException { props = new Properties(); try { props.loadFromXML(stream); } catch(Exception e) { System.err.println(e.getMessage()); } } protected Object handleGetObject(String schluessel) { return props.getProperty(schluessel); } public Enumeration getKeys() { Set schluessel = props.stringPropertyNames(); return Collections.enumeration(schluessel); } } }
32.2 Internationalisierung und Lokalisierung Wenn Sie Produkte für den internationalen Markt entwickeln, müssen Sie, wenn Sie sich nicht von vornherein mit geringen Absatzzahlen abfinden möchten, darauf achten, dass Ihre Produkte an die nationalen Eigenheiten der jeweiligen Zielmärkte angepasst sind. Wenn Sie beispielsweise Autos entwickeln und als neuen Markt die Britischen Inseln erobern möchten, sollten Sie darauf achten, dass die nach Großbritannien ausgelieferten Autos das Lenkrad auf der rechten Seite haben und mit englischem Handbuch und Armaturenbrett (Beschriftungen, Geschwindigkeit in mph und so weiter) ausgestattet sind. Gleiches gilt natürlich auch für die Software-Entwicklung. Die Bemühungen, ein Programm so zu implementieren, dass es möglichst gut für den internationalen Markt vorbereitet ist, bezeichnet man dabei als Internationalisierung, die Anpassung eines Programms an die nationalen Eigenheiten 906
Internationalisierung und Lokalisierung
eines Landes als Lokalisierung. Beide verfolgen letzten Endes den gleichen Zweck und beruhen zum Teil auf identischen Techniken (der Übergang zwischen Internationalisierung und Lokalisierung ist fließend und meist nur eine Frage der Sichtweise). Der wichtigste Schritt zur Lokalisierung eines Programms ist zweifelsohne die Übersetzung der in der Benutzerschnittstelle auftauchenden Texte. Aber auch die Unterstützung nationaler Eigenheiten bei der Formatierung von Datumsangaben, Gleitkommazahlen oder Währungsangaben sowie die Berücksichtigung des jeweiligen Alphabets bei Stringvergleichen gehören zur Lokalisierung und wollen berücksichtigt werden.
29 30
32.2.1 Lokale
31
Javas Unterstützung für die Internationalisierung von in Java geschriebener Software basiert auf zwei gewichtigen Säulen:
32
Unicode – Da Java intern Unicode zur Darstellung von Zeichen verwendet, kann es (grundsätzlich) sämtliche bekannten Zeichen verarbeiten.2 Die Reader- und Writer-Klassen von Java berücksichtigen automatisch die auf dem jeweiligen Rechner vorherrschende Zeichencodierung und wandeln automatisch von dieser Codierung in Unicode (Eingabe) oder umgekehrt (Ausgabe) um. Lediglich wenn Sie Daten einlesen, die anders codiert sind, oder Textdaten in einer anderen Codierung ausgeben wollen, müssen Sie auf InputStreamReader (bzw. OutputStreamReader) zurückgreifen, und die betreffende Zeichencodierung explizit angeben (siehe Kapitel 31.2, Abschnitt »Ein-/Ausgabe in Dateien«).
33 34 35 36
Lokale – Unter einer Lokale (Gebietsschema) versteht man eine politische, geografische oder kulturelle Region, mit eigener Sprache und eigenständigen Regeln für die Formatierung von Datumsangaben, Zahlen etc. In Java werden diese Lokale durch Instanzen der Klasse java.util.Locale repräsentiert.
37 38
Die Klasse Locale benötigen Sie für drei wichtige Aufgaben: ■ ■
■
Zur Abfrage der Standardlokale des aktuellen Systems. Zur Auswahl einer speziellen Ziel-Lokale (mit deren Hilfe Sie dann Lokale-spezifische Formatierungen und Ressourcen aktivieren können). Zur Erzeugung eigener Lokale (falls die gewünschte Ziel-Lokale nicht auf dem Rechner verfügbar ist). Allerdings müssen Sie dann auch die Unterstützung für die Lokale-spezifische Formatierungen implementieren.
39 40 41 42
2
Auch wenn Java alle Zeichen codieren kann, heißt dies nicht umgekehrt, dass ein Java-Programm alle beliebigen Zeichen auf jedem Rechner korrekt anzeigen kann. Dazu muss auch eine entsprechende Unterstützung, beispielsweise passende Fonts, auf dem Rechner installiert sein.
907
Ressourcen und Lokalisierung
Tabelle 32.1: Wichtige Methoden von Lokale
Methoden von Lokale
Beschreibung
Locale(String sprache)
Erzeugt eine Lokale für den angegebenen Sprachcode.
Locale(String sprache, String land)
Erzeugt eine Lokale für den angegebenen Sprach- und Ländercode.
Locale(String sprache, String land, String variante)
Erzeugt eine Lokale für den angegebenen Sprach-, Länder- und Umgebungscode. (Letzter gibt den Browser oder die Plattform an – beispielsweise WIN oder POSIX).
Object clone()
Zum Kopieren von Lokalen.
boolean equals(Object obj)
Zum Vergleichen von Lokalen. Zwei Lokale sind gleich, wenn ihre Sprach-, Länderund Umgebungscodes identisch sind.
static Locale[] getAvailableLocales()
Liefert eine Liste aller auf dem System verfügbaren Lokalen.
String getCountry() String getLanguage() String getVariant()
Liefert den Sprach-, Länder- bzw. Umgebungscode der Lokale zurück (gemäß ISO-639 und ISO-3166).
static Locale getDefault()
Liefert die aktuelle Standardlokale zurück.
String String String String
getDisplayCountry() getDisplayLanguage() getDisplayName() getDisplayVariant()
static void setDefault(Locale l)
Die Methoden getISOCountry() und getISOLanguage() liefern dreibuchstabige Codes.
Liefert Sprache, Land- und Umgebung der Lokale zurück (in decodierter Form, zur Anzeige auf der Benutzeroberfläche).
Richtet die angegebene Lokale als Standardlokale ein.
Lokale abfragen Grundsätzlich gilt für die Internationalisierung, dass Sie die in Java eingebaute Lokale-Unterstützung nur indirekt nutzen, d.h., Sie rufen für sprachund länderspezifische Aufgaben die in der Java-Bibliothek definierten lokale-spezifischen Methoden auf und profitieren davon, dass die Java Virtual Machine automatisch beim Start die Lokale des Systems ermittelt und als Standardlokale einrichtet. Wenn Sie dennoch einmal direkten Zugriff auf die Standardlokale benötigen, beispielsweise um den Anwender über sein Gebietsschema zu informieren, zum Vergleichen mit einer erwarteten Wunsch-Lokale, zum Debuggen Ihrer Anwendung oder auch um Sie explizit an eine Methode zu übergeben (Currency.getInstance(Locale l) ist zum Beispiel eine der wenigen lokale-spezifischen Methoden, die nicht standardmäßig mit der aktuellen Lokale arbeitet, sondern stets eine Lokale als Argument verlangt), so liefert Ihnen getDefault() die Standardlokale zurück:
908
Internationalisierung und Lokalisierung
Locale l = Locale.getDefault();
Wenn Sie ermitteln wollen, welche Lokale auf einem System installiert sind, rufen Sie dazu die Methode getAvailableLocales() auf: // Alle verfügbaren Lokale auflisten Locale[] liste = Locale.getAvailableLocales(); for (int i = 0; i < liste.length; ++i) System.out.println(liste[i]);
29
Anhand dieser Liste können Sie dann beispielsweise feststellen, ob eine bestimmte Lokale, die Sie verwenden möchten, vorhanden ist.
30
Auszug aus der Liste der verfügbaren Lokale:
31
ar ar_AE ar_BH ar_DZ ar_EG ar_IQ ar_JO ar_KW ar_LB ar_LY ar_MA ar_OM ar_QA ar_SA ar_SD ar_SY ar_TN ar_YE hi_IN iw iw_IL ja ja_JP ko ko_KR th th_TH th_TH_TH zh zh_CN zh_HK zh_TW ...
32 33 34 35 36 37 38 39 40 41
Lokale erzeugen 42
Eigene Lokale erzeugen Sie mithilfe der Konstruktoren von Locale. Dazu übergeben Sie wahlweise den Sprachcode, Sprach- und Ländercode oder, in seltenen Fällen, Sprach-, Länder- und Umgebungscode.
909
Ressourcen und Lokalisierung
Locale eigene = new Locale("de"); Locale eigene = new Locale("de","CH");
// Lokale für deutsch // Lokale für deutsch, Schweiz
Die Sprachcodes sind durch ISO-639 standardisiert, die Ländercodes durch ISO-3166. (Vorsicht! Die Standards verändern sich gelegentlich.) Tabelle 32.2: Ausgesuchte Sprach- und Ländercodes
Sprachcode
Sprache
Ländercode
Land
ar
Arabisch
LB
Libanon
da
Dänisch
DK
Dänemark
de
Deutsch
DE
Deutschland
CH
Schweiz
AT
Österreich
el
Griechisch
GR
Griechenland
en
Englisch
GB
Großbritannien
US
USA
AU
Australien
CA
Kanada
ES
Spain
CO
Kolumbien
FR
Frankreich
BE
Belgien
CA
Kanada
eo
Esperanto
es
Spanisch
fr
910
Französisch
it
Italienisch
IT
Italien
ja
Japanisch
JP
Japan
nl
Niederländisch
NL
Niederlande
no
Norwegisch
NO
Norwegen
sv
Schwedisch
SE
Schweden
tr
Türkisch
TR
Türkei
zh
Chinesisch
CN
China
HK
Hongkong
TW
Taiwan
Internationalisierung und Lokalisierung
Vollständige Listen finden Sie im Internet. Sie können Sie sich aber auch selbst ausdrucken. Die Locale-Methoden getISOLanguages() und getISOCountries() liefern die Codes nach ISO-639 und ISO-3166 zurück. Die Erzeugung einer eigenen Lokale instanziiert lediglich ein Locale-Objekt, das die gewünschte Lokale im Quelltext repräsentiert. Die mit dieser Lokale verbundenen, lokale-spezifischen Formatierungen sind nur verfügbar, wenn eine entsprechende Lokale auf dem aktuellen Rechner installiert ist oder Sie eine entsprechende Unterstützung implementieren.
29
HALT
30
Lokale ändern Wozu definiert man eigene Locale?
31
Ein Einsatzgebiet ist die Formatierung lokale-spezifischer Ausgaben für andere Gebietsschemata. Angenommen Sie wollen Aktienkurse in Euro und Dollar angeben.
32
Natürlich können Sie die Ausgabe direkt formatieren: String ausgabe = " " + preisEuro + " €" + "\n" + " $" + preisDollar;
33
Sie können die Formatierung aber auch über Lokale steuern:
34
NumberFormat nf; String ausgabe = "";
35
double preisEuro = 12.5; double preisDollar = 12.9;
36 // Standardlokale verwenden nf = NumberFormat.getCurrencyInstance(); ausgabe += " Unsere Aktie: \n\n" + " " + nf.format(preisEuro) + "\n";
37
// US-Lokale verwenden nf = NumberFormat.getCurrencyInstance(new Locale("en", "US")); ausgabe += " " + nf.format(preisDollar) + "\n";
38
Abbildung 32.1: Währungsformatierung mit Lokalen
39 40 41 42
911
Ressourcen und Lokalisierung
HALT
Die Formatierung mit Lokalen ist zwar flexibler, hängt aber stark davon ab, dass entsprechende Lokale auf dem aktuellen System installiert und korrekt konfiguriert sind (was nicht immer der Fall ist). Schließlich können Sie die Standardlokale ganz umstellen, beispielsweise um explizit eine für Frankreich lokalisierte Version zu erstellen: Locale save = Locale.getDefault(); Locale.setDefault(new Locale("fr", "FR")); ... Locale.setDefault(save);
32.2.2 Ressourcendateien (und -bündel) Benutzerschnittstellen, die mithilfe von Ressourcenbündeln erstellt wurden (siehe oben), können in Java äußerst komfortabel lokalisiert werden. Sie müssen lediglich für jede Lokale, die Sie unterstützen möchten, eine eigene lokalisierte Ressourcendatei erzeugen. Angenommen Ihre Ressourcendatei heißt ressourcen.properties und Sie möchten Ihre Software in USA und Deutschland vertreiben. Dann sollten Sie zwei Kopien von ressourcen.properties erstellen, diese in ressourcen_de_DE. properties und ressourcen_en_US.properties umbenennen und die Strings in den Ressourcendateien übersetzen (bzw. übersetzen lassen). Listing 32.8: ressourcen.properties – allgemeine Ressourcendatei # ressourcen.properties für alle Lokalen # Willkommen = Willkommen Feuchtigkeit = Feuchtigkeit Angebot = gewinnen Sie
Listing 32.9: ressourcen_de_DE.properties – Kopie für Deutschland # ressourcen_de_DE.properties für Deutsch/Deutschland # Willkommen = Willkommen Feuchtigkeit = Feuchtigkeit Angebot = gewinnen Sie
Listing 32.10: ressourcen_en_US.properties – Kopie für USA # ressourcen.properties für Englisch/USA # Willkommen = Welcome Feuchtigkeit = humidity Angebot = win
Wenn Sie im Programm die Ressourcendatei wie in Abschnitt »Strings laden« mit getBundles(String) öffnen: 912
Internationalisierung und Lokalisierung
try { resources = ResourceBundle.getBundle("ressourcen"); } catch ...
müssen Sie den Programmcode weder verändern noch neu kompilieren. Ihr Code wählt zur Laufzeit automatisch die Ressourcendatei aus, die am besten zur Standardlokale des aktuellen Systems passt.
29 Das Beispielprogramm zur Verwendung lokale-spezifischer Ressourcendateien finden Sie auf der Buch-CD im Verzeichnis Kapitel32\Lokale.
30
TIPP
getBundle() Die Methode getBundle() gibt es in sechs überladenen Versionen:
31
public static ResourceBundle getBundle(String name) public static ResourceBundle getBundle(String name, Locale l) public static ResourceBundle getBundle(String name, Locale l, ClassLoader cl) public static ResourceBundle getBundle(String name, Locale l, ClassLoader cl, ResourceBundle.Control obj) public static ResourceBundle getBundle(String name, Locale l, ResourceBundle.Control obj) public static ResourceBundle getBundle(String name, ResourceBundle.Control obj)
32 33 34
Alle sechs Methoden suchen nach der Ressourcenbündel-Klasse oder -Datei, die am besten zu der angegebenen bzw. der Standardlokale passt. Die möglichen Kandidaten ergeben sich durch Erweiterung des Namens um die Sprach-, Land- und Umgebungscodes der Lokale (soweit diese Codes für das übergebene Locale-Objekt angegeben sind).
35
Wenn Sie beispielsweise getBundle(ressourcen, new Locale("en", "US") aufrufen und die Standardlokale ist Locale("de", "DE", "WIN"), sind die möglichen Kandidaten nach Spezifizität geordnet:
37
36
38
ressourcen_en_US ressourcen_en ressourcen_de_DE_WIN ressourcen_de_DE ressourcen_de ressourcen
39 40
Diese Liste arbeitet die Methode von oben nach unten ab und sucht zuerst nach einer entsprechenden Class-Datei, die das gewünschte Ressourcenbündel implementiert. Kann es eine solche Klasse nicht finden, sucht sie nach einer entsprechenden Ressourcendatei mit der Extension .properties. (Enthält der Name eine Paketangabe, werden die Punkte . dafür in Schrägstriche / umgewandelt.) Die Datei, die sie als Erstes findet (und die somit am besten passt), wird geladen.
41 42
913
Ressourcen und Lokalisierung
Wenn Sie getBundle(String) aufrufen, wird nur nach den möglichen Kandidaten für die Standardlokale gesucht.
32.2.3 Nationale Formatierungen Die verschiedenen Lokale unterscheiden sich zumeist nicht nur in der Sprache, sondern auch in der Art und Weise wie sie Datumsangaben, Gleitkommazahlen und Preisangaben darstellen. Die Java-Bibliothek definiert zur Formatierung dieser Daten spezielle Formatierungsklassen. Diese berücksichtigen per Voreinstellung die eingerichtete Standardlokale, können aber auch für andere Lokale erzeugt werden. Die wichtigsten Formatierungsklassen sind ■ ■
TIPP
java.text.DateFormat für Datums- und Zeitangaben sowie java.text.NumberFormat für Gleitkommazahlen, Preis- und Prozentangaben
Ob diese Klassen die richtige Formatierung erzeugen, hängt davon ab, ob eine entsprechende Unterstützung für die gewünschte Lokale vorhanden ist. Beide Klassen stellen die Methode getAvailableLocales() zur Verfügung, die eine Liste der Lokale zurückliefert, die die notwendige Unterstützung anbieten. (Beachten Sie, dass nicht alle auf einem System verfügbaren Lokale (vergleiche Locale.getAvailableLocales()) alle lokale-spezifischen Formatierungen unterstützen müssen.) Datums- und Zahlenformate Um eine Datums-/Zeitangabe gemäß der Standardlokale zu formatieren, schreiben Sie: Date datum = new Date(); DateFormat df = DateFormat.getDateInstance(DateFormat.LONG); DateFormat tf = DateFormat.getTimeInstance(DateFormat.LONG); String ausgabe = df.format(datum) + " " + tf.format(datum);
Um eine Datums-/Zeitangabe gemäß einer beliebigen Lokale zu formatieren, übergeben Sie die Lokale im getXxxInstance()-Aufruf: datum = new Date(); df = DateFormat.getDateInstance(DateFormat.LONG, new Locale("en")); tf = DateFormat.getTimeInstance(DateFormat.LONG, new Locale("en")); ausgabe = df.format(datum) + " " + tf.format(datum);
Zahlenformate Um Gleitkommazahlen gemäß der Standardlokale zu formatieren, schreiben Sie: double zahl = 12.5; NumberFormat nf = NumberFormat.getNumberInstance(); String ausgabe = nf.format(zahl);
914
Internationalisierung und Lokalisierung
Um eine Gleitkommazahl gemäß einer beliebigen Lokale zu formatieren, übergeben Sie die Lokale im getNumberInstance()-Aufruf: nf = NumberFormat.getNumberInstance(new Locale("en"));
Formatierer für Prozentangaben erzeugen Sie mit NumberFormat.getPercentInstance():
29
double feuchtigkeit = 0.652; NumberFormat nf = NumberFormat.getPercentInstance(); String ausgabe = nf.format(feuchtigkeit);
30
beziehungsweise feuchtigkeit = 0.652; nf = NumberFormat.getPercentInstance(new Locale("en")); ausgabe = nf.format(feuchtigkeit);
31
Preisangaben (Währungssymbol)
32
Um Preisangaben gemäß der Standardlokale zu formatieren, schreiben Sie: double preis = 12.5; NumberFormat nf = NumberFormat.getCurrencyInstance(); String ausgabe = nf.format(preis);
33
Um eine Preisangabe gemäß einer beliebigen Lokale zu formatieren, übergeben Sie die Lokale im getCurrencyInstance()-Aufruf:
34
nf = NumberFormat.getCurrencyInstance(new Locale("en"));
Das folgende Programm demonstriert die Verwendung lokale-spezifischer Ressourcen und Formatierungen. Es greift je nach eingestellter Standardlokale auf eine der folgenden Ressourcendateien zu:
35 Beispiel
36
ressourcen_de_DE ressourcen_en_US ressourcen_fr_FR ressourcen
37
und erzeugt lokale-spezifische Ausgaben von Datum-, Zeit-, Preis- und Prozentangaben.
38
Grundsätzlich würde man das Programm so implementieren, dass es einfach mit der Standardlokale arbeitet, und zum Testen dann die Lokale des Betriebssystems ändern. (Unter Windows über die Systemsteuerung, Symbol LÄNDEREINSTELLUNGEN bzw. REGIONS- UND SPRACHOPTIONEN). Da dies aber mit einem erneuten Hochfahren des Rechners verbunden ist, verwendet das Programm nicht die Lokale des Systems, sondern richtet in einer for-Schleife nacheinander de_De, en_US und fr_FR als zu verwendende Standardlokale ein.
39 40 41
Um die Ausgabe nicht durch die Zeichencodierung der Konsole zu verfälschen, verwendet das Programm einen Meldungsdialog.
42
915
Ressourcen und Lokalisierung
Listing 32.11: Lokale.java import import import import
java.io.*; java.util.*; java.text.*; javax.swing.*;
public class Lokale { public static ResourceBundle resources; public static void main(String[] args) throws IOException, ParseException { Date datum; DateFormat df, tf; NumberFormat nf; String ausgabe = ""; String[] sprachen = {"de", "en", "fr"}; String[] laender = {"DE", "US", "FR"}; for(int i = 0; i < sprachen.length; ++i) { Locale.setDefault(new Locale(sprachen[i], laender[i])); ausgabe += "Lokale: " + sprachen[i] + "\n"; // Ressourcen verfügbar machen try { resources = ResourceBundle.getBundle("ressourcen"); } catch (MissingResourceException e) { System.err.println(" Ressourcendatei nicht gefunden"); } // Strings aus Ressourcenbündel ausgeben ausgabe += " " + Lokale.resources.getString("Willkommen") + "\n"; // Datum und ausgeben datum = new Date(); df = DateFormat.getDateInstance(DateFormat.LONG); tf = DateFormat.getTimeInstance(DateFormat.LONG); ausgabe += " " + df.format(datum) + " " + tf.format(datum) + "\n"; double feuchtigkeit = 0.652; nf = NumberFormat.getPercentInstance(); nf.setMaximumFractionDigits(2); ausgabe += " " + Lokale.resources.getString("Feuchtigkeit") + ": " + nf.format(feuchtigkeit) + "\n"; double preis = 12.5; nf = NumberFormat.getCurrencyInstance(); ausgabe += " " + Lokale.resources.getString("Angebot") + ": " + nf.format(preis) + "\n";
916
Internationalisierung und Lokalisierung
ausgabe += "\n"; } javax.swing.JOptionPane.showMessageDialog(null , ausgabe); System.exit(0); }
29
}
Abbildung 32.2: Ausgabe des Programms Lokale.java
30 31 32 33 34 35 36 37
32.2.4 Stringvergleiche 38
Wenn Sie lexikografische Stringvergleiche durchführen, sollten Sie grundsätzlich die Reihenfolge der Zeichen in den nationalen Alphabeten berücksichtigen. Java stellt hierzu die Klasse java.text.Collator zur Verfügung. ■ ■
39
Collator.getInstance() liefert Ihnen eine Instanz, die bei Stringvergleichen die Standardlokale verwendet. Collator.getInstance(loc) liefert Ihnen eine Instanz, die bei Stringvergleichen die Lokale loc verwendet.
40
Den eigentlichen Vergleich führen Sie mit der compare()-Methode der Collator-Instanz durch:
41
String a = "pègre"; String b = "Pègre";
42
if( a.compareTo(b) < 0 ) JOptionPane.showMessageDialog(null, a + " < " + b);
917
Ressourcen und Lokalisierung
if( a.compareTo(b) > 0 ) JOptionPane.showMessageDialog(null, a + " > " + b); if( a.compareTo(b) == 0 ) JOptionPane.showMessageDialog(null, a + " == " + b); Collator frColl = Collator.getInstance(new if( frColl.compare(a, b) < 0 ) JOptionPane.showMessageDialog(null, a + if( frColl.compare(a, b) > 0 ) JOptionPane.showMessageDialog(null, a + if( frColl.compare(a, b) == 0 ) JOptionPane.showMessageDialog(null, a +
Locale("fr","FR")); " < " + b); " > " + b); " == " + b);
Hier stellt die erste Vergleichserie (ohne Collator) fest, dass pègre größer als Pègre ist, weil im Unicode-Zeichensatz die Kleinbuchstaben erst hinter den Großbuchstaben kommen (folglich höhere Unicode-Werte haben). Die zweite Vergleichsserie erkennt richtig, dass pègre kleiner als Pègre ist, weil sie für die Vergleiche die korrekte Reihenfolge der Zeichen im nationalen Alphabet zugrunde legt.
TIPP
Mit der Instanzmethode setStrength(staerke) können Sie einstellen, wie streng das Collator-Objekt beim Vergleichen sein soll. Mögliche Werte zunehmender Stärke sind die Konstanten Collator.PRIMARY, Collator. SECONDARY, Collator.TERTIARY und Collator.IDENTICAL. Welche Zeichen ab welcher Stufe als unterschiedlich angesehen werden, hängt von den verschiedenen Sprachen ab. Unterschiede zwischen Klein- und Großbuchstaben werden meist ab Stufe Collator.TERTIARY erkannt. Voreinstellung ist Collator.IDENTICAL.
918
Teil 6 Spezielle Programmiergebiete 921
Datenbankzugriffe mit JDBC
33
947
JDBC – Vertiefung
34
967
Netzwerkprogrammierung
35
1001
HTTP-Verbindungen
36
1017
Verteilte Anwendungen mit RMI
37
1041
Applets
38
1063
JNI, eine Schnittstelle zu C/C++
39
1083
Sicherheit
40
1097
Annotationen
41
1103
XML
42
1129
Webservices
43
Inhalt
33 Datenbankzugriffe mit JDBC 29 30
Was Computer am besten beherrschen, ist die Verwaltung von Informationen – den Daten. Fast jedes größere Programm, insbesondere im professionellen Bereich, hat daher eine Komponente, die sich mit der Speicherung von mehr oder weniger großen Datenmengen beschäftigt. Eine solche Komponente nennt man üblicherweise »Datenbank«.
31 32
Datenbank-Grundlagen
33
Datenbanken können ganz unterschiedlich implementiert sein. In einfachen Fällen verbirgt sich hinter einer Datenbank eine normale Textdatei, in die Sie die Daten zeilenweise schreiben als auch zeilenweise aus ihr auslesen können. Schnell hat sich aber gezeigt, dass sehr große Datenmengen bei zunehmender Geschwindigkeit einen Flaschenhals-Effekt verursachen, und es wurden bessere Verfahren zur Umsetzung von Datenbanken entwickelt.
34 35
33.1.1 Die relationale Datenbank
36
Für die meisten Zwecke hat sich dabei die Technik der relationalen Datenbank durchgesetzt. Die Idee ist hierbei, die Daten in Tabellenform zu organisieren, d.h., man unterscheidet Zeilen (rows) und Spalten (columns), die zu Tabellen zusammengefasst sind. Eine Zeile wird auch als Datensatz bezeichnet, während eine Spalte Feld genannt wird.
37 38 Abbildung 33.1: Tabelle mit sieben Datensätzen zu je sieben Feldern (Spalten)
39 40 41 42
921
Index
33.1
Datenbankzugriffe mit JDBC
Ein vollständiger Datensatz ist eine Zeile, in der jedes Feld einen gültigen Wert zugewiesen bekommt (wie in der obigen Abbildung). Das muss aber nicht unbedingt so sein, da man auch Tabellen definieren kann, in denen nicht jedes Feld definiert sein muss (z. B. bei einer Adressdatenbank, in der Name und Straße gesetzt werden müssen, während die Telefonnummer optional ist). Die Werte eines Feldes sind, ähnlich wie in Java, von einem bestimmten Datentyp (z. B. ein boolescher Typ oder ein String). Ferner können Beziehungen (Relationen) zwischen Tabellen definiert werden. Die so strukturierten Daten werden natürlich letztlich auch in Dateien abgespeichert. Diese weisen jedoch eine spezielle Struktur auf, welche den Zugriff und die Suche deutlich beschleunigt. Die Software für Zugriff und Speicherung nennt man übrigens Datenbank-Management-System (DBMS) und, im Falle von relationalen Datenbanken, RDBMS.
TIPP
Umgangssprachlich wird häufig kein besonderer Unterschied zwischen Datenbank und DBMS gemacht. Manchmal ist mit Datenbank allerdings speziell die jeweils zusammengehörende Datenmenge gemeint, die von einem DBMS verwaltet wird. Ein DBMS kann also mehrere Datenbanken haben. Die wichtigste Arbeit in einer Datenbank ist immer die Suche nach Datensätzen, die bestimmten Suchkriterien entsprechen. Um diese Suche effizient zu gestalten, werden vom DBMS spezielle Indizes erstellt. Ein Index enthält dabei im Prinzip die sortierten Einträge einer Spalte (über alle Zeilen hinweg) sowie die Position der zugehörigen Datensätze in der zugrunde liegenden Datei. In der Frühzeit der Datenbanken war es häufig so, dass jedes Programm, das eine Datenbank-Funktionalität benötigte, diese selbst zur Verfügung stellte, natürlich jeweils mit einem eigenen Dateiformat, einer eigenen Variante zum Zugriff auf die Daten, usw. Die Vielfalt war groß und teuer! Mittlerweile hat sich eine Trennung von Anwendung und Datenbank durchgesetzt. Datenbanken werden durch separate DBMS realisiert, die eine standardisierte Schnittstelle anbieten, über die andere Anwendungen auf die Datenbank zugreifen können, d.h., es wird ein Client/Server-Prinzip eingesetzt: An einer zentralen Stelle (häufig auf einem sehr schnellen Serverrechner) läuft das DBMS und wartet auf Anfragen von Clients, den Anwendungen. Gute DBMS können dabei völlig verschiedene Datenbanken gleichzeitig und völlig getrennt verwalten. Man kann also z. B. die Daten der Personalabteilung und die Daten der Lagerhaltung im gleichen DBMS unterbringen (aus Sicherheitsgründen macht man dies aber häufig nicht). Leistungsfähige DBMS sind sehr teuer. Eine Einzelplatzlizenz kann leicht 5000 € oder mehr kosten. Häufig werden die Lizenzkosten auch noch an die Anzahl der CPUs oder neuerdings CPU-Kerne und deren Taktfrequenz gekoppelt, sodass schnelle Rechner (die selbst schon teuer sind) die Lizenz-
922
Datenbank-Grundlagen
kosten weiter hochtreiben. Nicht zuletzt wegen dieser Kosten haben sich einige Open Source-Projekte mit der Entwicklung von kostenlosen DBMS beschäftigt, ohne bei der Leistungsfähigkeit Abstriche zu machen. Ein herausragender Vertreter hierfür ist MySQL, welches sich hinsichtlich der Geschwindigkeit und Stabilität mit jedem kommerziellen Produkt messen kann, ja häufig sogar deutlich besser ist! Da es zudem kostenlos verfügbar ist, wird in diesem Buch MySQL für die Beispiele zur Datenbankprogrammierung eingesetzt. Das Setup-Programm finden Sie auf der Buch-CD im Verzeichnis Software. Die Installation wird im Buchanhang beschrieben.
29
Seit Java 6 gibt es noch eine weitere, sehr interessante Möglichkeit, mit Datenbank-Unterstützung zu arbeiten: Java-DB, welches auf der Open Source Datenbank Apache Derby basiert. In Abschnitt 33.5 werden wir den Datenbankzugriff mit Java-DB kurz vorstellen.
31
30
32
33.1.2 JDBC und ODBC Um ein DBMS von einer Anwendung aus anzusprechen, muss man die jeweilige angebotene Programmierschnittstelle (API) verwenden. Um nun ein Programm nicht für jedes mögliche DBMS erstellen bzw. umschreiben zu müssen, hat sich die Methode der Datenbanktreiber durchgesetzt. Ein solcher Treiber ist eine Software-Schicht, die Datenbank-Aufrufe, die in einer bestimmten Programmiersprache (z. B. Java, C++) erstellt wurden, in die Kommandos der jeweiligen DBMS-API umwandelt. Anders formuliert: Der Treiber stellt die Verbindung zwischen dem Programm und der Datenbank her.
33 34 35 36
Auf der Programmebene sind ebenfalls Programmierschnittstellen für Datenbankzugriffe entstanden. Die beiden wichtigsten sind ODBC (Open Database Connectivity), eine von Microsoft entwickelte API für die Sprache C, sowie JDBC (Java Database Connectivity) von Sun für die Sprache Java.
37
Da sich dieses Buch mit Java befasst, ist für uns vornehmlich JDBC von Interesse. JDBC ist fester Bestandteil von Java und bietet eine Reihe von Schnittstellen und Methoden zum Zugriff auf Datenbanken. Alle üblichen Datenbanksysteme bieten mittlerweile einen rein Java-basierten (sogenannten Typ-4) JDBC-Treiber.
38 39 40
33.1.3 SQL – Structured Query Language Der eigentliche Kontakt mit einem DBMS erfolgt aus Sicht des Anwenders oder Programmierers mithilfe einer halbwegs standardisierten Sprache namens SQL.
41 42
SQL bietet, ähnlich wie eine Programmiersprache, Befehle an, mit denen sich die typischen Aufgaben beim Umgang mit einer Datenbank definieren lassen. SQL ist mittlerweile sehr umfangreich, sodass an dieser Stelle nur eine kurze Einführung gegeben werden soll. Wenn Sie komplexe Daten923
Datenbankzugriffe mit JDBC
bankanwendungen erstellen wollen, dann müssen Sie sich SQL-Spezialliteratur zulegen. Im Folgenden werden nur die SQL-Befehle und ihre Syntax beschrieben, mit denen sich fundamentale Aktionen und die Beispiele nachvollziehen lassen.
TIPP
SQL-Befehle und Schlüsselwörter sind »case-insensitiv«, d.h., es wird nicht zwischen Groß- und Kleinschreibung unterschieden. Es ist allerdings üblich, Großbuchstaben zu verwenden. Tabellen anlegen – CREATE TABLE Das Anlegen einer Tabelle besteht in der Vergabe eines Tabellennamens sowie der Definition der Spalten. Meistens erfolgt das Anlegen von Tabellen im DBMS über eine GUI mit geeigneten Eingabemasken. Es ist allerdings auch über einen SQL-Ausdruck aus einem Anwendungsprogramm heraus machbar – mithilfe des CREATE TABLE-Befehls: CREATE TABLE tabellenname ( spaltenname spaltentyp spaltenmodifzierer, ..., spaltenname spaltentyp spaltenmodifzierer);
Beispiel: CREATE TABLE interpreten ( nummer INTEGER NOT NULL PRIMARY KEY, name VARCHAR(30) NOT NULL, vorname VARCHAR(30) NOT NULL, plattenlabel VARCHAR(30) );
SQL-Anweisungen müssen bei den meisten DBMS mit einem Semikolon abgeschlossen werden! HALT
In diesem Beispiel wird eine Tabelle namens interpreten angelegt, die aus den Spalten nummer, name, vorname und plattenlabel besteht. Die erste Spalte ist vom Typ INTEGER, die anderen haben als Typ VARCHAR(30), also variable Zeichenketten bis maximal Länge 30. nummer, name und vorname haben darüber hinaus noch einen Modifizierer (Felder), der zusätzliche Bedingungen festlegt. Die wichtigsten Modifizierer bei MySQL sind: Tabelle 33.1: MySQLModifizierer
924
Modifizierer
Beschreibung
AUTO_INCREMENT
Nimmt den bisherigen Maximalwert der Spalte und addiert eins hinzu. Bei INSERT muss für eine solche Spalte NULL übergeben werden.
DEFAULT
Speichern eines Standardwerts, wenn bei INSERT kein Wert definiert wird.
NOT NULL
Spalte darf nicht leer bleiben.
Datenbank-Grundlagen
Modifizierer
Beschreibung
PRIMARY KEY
Spalte wird zur Erstellung eines Index verwendet. Jede Zeile muss in dieser Spalte einen anderen Wert enthalten.
UNIQUE
Ein Wert darf in dieser Spalte nur einmal vorkommen.
UNSIGNED
Bei numerischen Datentypen die Angabe, dass nur nicht-negative Werte vorkommen dürfen.
Tabelle 33.1: MySQLModifizierer (Forts.)
29 30
Es gibt eine Vielzahl von SQL-Datentypen, die leider bei jedem DBMS variieren. Die Wichtigsten für MySQL sind: MySQL Typ
Beschreibung
CHAR(n)
Feste Zeichenkette der Länge n (1
Damit sind hoffentlich alle Hürden genommen. Auf dem Webserver werden die HTML-Seite und das zugehörige JAR-Archiv abgelegt, und ein Client kann dann über seinen Browser darauf zugreifen. Abbildung 34.1: Zugriff auf MySQL per Webbrowser
Das Verzeichnis zu diesem Kapitel auf der Buch-CD enthält im Unterverzeichnis MySQLApplet ein kleines Beispiel zum Anzeigen einer Tabelle per Applet. Falls Sie eine Firewall auf Ihrem Rechner laufen haben, müssen Sie diese möglicherweise abschalten oder umkonfigurieren, um das Applet testen zu können.
964
Datenbank-Zugriffe über Applets
Wenn Sie kein Netzwerk bzw. Webserver zur Verfügung haben und das Datenbank-Applet testen, indem Sie die Webseite MySQLApplet.html vom lokalen Dateisystem aus in den Browser laden, müssen Sie unter Umständen die Zugriffsrechte in der Datei .java.policy aus dem Home-Verzeichnis des Benutzers3 per Hand oder mit dem Programm policytool anpassen (Berechtigung: PropertyPermission, Zielname: file.encoding, Aktionen: read sowie Berechtigung: SocketPermission, Zielname: localhost, Aktionen: accept, listen, connect, resolve). Mehr zu Sicherheitsrichtlinien und dem policytool finden Sie im Kapitel 40.
TIPP
29 30 31 32
33 34 35 36 37 38 39 40 41 42 3
Unter Windows ist dies das Verzeichnis c:\Dokumente und Einstellungen\Benutzername bzw. c:\Users\Benutzername. Möglicherweise müssen Sie diese Datei erst anlegen (wobei Sie den führenden Punkt im Dateinamen nicht vergessen dürfen).
965
Inhalt
35 Netzwerkprogrammierung 29 30
Computer miteinander zu vernetzen ist eigentlich eine uralte Idee und kam schon in größerem Umfang in den sechziger Jahren des letzten Jahrhunderts zur Anwendung. Mittlerweile hat sich in Hinblick auf Programmierung und Technologien zwar viel getan, aber die Motivation ist die gleiche. Computernetzwerke dienen im Wesentlichen zum Austausch von Daten und dem gemeinsamen Nutzen von raren Ressourcen, z. B. Hochleistungsdruckern, Backup-Speichern, etc.
31 32 33
Netzwerke 34
Netzwerke lassen sich in zwei Kategorien unterteilen: ■
■
LAN (local area network) sind lokale Netze, bei denen alle Rechner zum gleichen Unternehmen oder der gleichen Person gehören und in der Regel auch physisch benachbart sind, also innerhalb eines Gebäudes oder einer Stadt. WAN (wide area network) sind eigentlich nur die großen Brüder der LANs und verbinden Rechner, die ortsmäßig deutlich voneinander getrennt sind, z. B. in verschiedenen Städten oder Kontinenten.
35 36 37
Das mittlerweile bekannteste Netzwerk ist das legendäre Internet, das von seiner Art her eine Mischung von LAN und WAN ist und Zehntausende von lokalen Subnetzen über besondere Rechner (Gateways) zu einem weltumspannenden Netz verknüpft.
38
Das Vernetzen von Rechnern besteht aus einem zweistufigen Verfahren. Zunächst einmal muss die physikalische Verbindung hergestellt werden, d.h., es müssen Kabel, Verstärker, Verteiler und etliches andere installiert werden. Dabei gibt es diverse Methoden, die Rechner miteinander zu verbinden: z. B. ringförmig oder sternförmig. Ist diese Infrastruktur vorhanden, muss man den Rechnern beibringen, wie sie miteinander »reden« sollen und wie Daten ausgetauscht werden.
39 40 41
Hierfür ist das Client-Server-Modell entwickelt worden, nach dem fast jede Kommunikation zwischen zwei Rechnern abläuft: der Rechner (genauer: das auf ihm ablaufende Programm), der etwas möchte, ist der Client und kontaktiert den Rechner, der das Gewünschte liefern kann (der Server). Dabei kann und ist häufig jeder angeschlossene Rechner mal Client, mal
42
967
Index
35.1
Netzwerkprogrammierung
Server, je nachdem, um welche Daten oder Dienste es sich handelt. Allerdings hat es sich eingebürgert, bestimmte Dienste (Services), die sehr wichtig sind und daher häufig benötigt werden, auf dafür reservierten Rechnern ablaufen zu lassen und dort keine anderen Aktivitäten zuzulassen; solche Rechner sind in der Regel nur Server und nehmen eher selten eine ClientRolle ein. Wenn ein Client einen Server ansprechen will, muss aber noch festgelegt werden, wie die beiden miteinander reden sollen, also wie die Kommunikation genau ablaufen soll. Es wird ein Kommunikationsmodell benötigt, welches regelt, wer wann welche Daten in welchem Format schickt. Da wir in einer genormten Welt leben, gibt es auch hierfür eine Norm, nämlich das ISO-OSI-Referenzmodell, das ein siebenschichtiges Kommunikationsmodell vorsieht (Abbildung 35.1). Abbildung 35.1: Das ISO-OSI-Referenzmodell
Rechner 1
Rechner 2
Anwendung
Anwendung
Präsentation
Präsentation
Session
Session
Transport
Transport
Netzwerk
Netzwerk
Sicherung
Sicherung
Physikalisch
Physikalisch
Jede Schicht in diesem Modell ist für einen Aspekt der Kommunikationsdurchführung verantwortlich. Wenn Programm A auf Rechner 1 mit Programm B auf Rechner 2 kommunizieren will, wird von A in der Anwendungsschicht (application layer) eine Nachricht erzeugt und an die Präsentationsschicht (presentation layer) weitergereicht (z. B. durch Aufruf einer Bibliotheksroutine des Betriebssystems), die eventuell vor/hinter die Nachricht einen eigenen Block mit Informationen setzt (sogenannte Header und Trailer) und die Nachricht weiterreicht an die darunter liegende Sessionsschicht. Die wiederum fügt gegebenenfalls einen weiteren Header hinzu und reicht die Nachricht weiter nach unten. Dieses Spiel geht weiter bis zur untersten Schicht. Jede Schicht fügt Header und Trailer hinzu oder bearbeitet die Nachricht (z. B. Kompression oder Verschlüsselung). Die physikalische Schicht schließlich sendet die Nachricht, indem die Daten in einen Bitstream umgewandelt und über diverse Kabel gesendet werden und
968
Netzwerke
schließlich am Zielrechner ankommen. Dort wandert dann die Nachricht ganz analog von der untersten Schicht bis hin zum Empfänger (das Programm B) die Schichten wieder hoch. Header werden entfernt, es wird dekomprimiert usw. Für jede Schicht wird zusätzlich noch eine Menge an Regeln benötigt, die festlegen, wie genau die Kommunikation erfolgen soll. So etwas nennt sich Protokoll.
29
35.1.1 Der TCP/IP-Stack
30
Der Vorteil eines Schichtenmodells ist, dass man definierte Schnittstellen hat (ein Austausch von Daten findet nur zwischen benachbarten Schichten statt) und die notwendige Funktionalität klar den einzelnen Schichten zugeordnet ist.
31 32
Das gezeigte ISO-Modell ist allerdings in den heutigen Rechnernetzen nicht zu finden, da es zu kompliziert ist und zu viel Overhead mit sich bringt. Stattdessen wird ein vereinfachtes Schichtenmodell eingesetzt, das kurzerhand TCP/IP-Stack genannt wird (Abbildung 35.2). TCP/IP Stack
ISO-OSI Modell
33 Abbildung 35.2: Der TCP/IP-Stack
34
Anwendung
35 Anwendung
Präsentation
36
Session Transport
Transport
Internet
Netzwerk
37 38
Sicherung Netzwerk
39
Physikalisch
Die unterste Schicht »Netzwerk« entspricht der physikalischen Schicht (physical layer) und der Sicherungsschicht (data link) aus dem ISO-OSIModell und befasst sich mit dem physikalischen Transfer der Daten, d.h., hier werden die Bits geschickt, indem sie in kleine Päckchen – die Frames – unterteilt werden, mit Prüfsummen versehen und abgeschickt bzw. empfangen werden.
40 41 42
Die darüberliegende Internetschicht (internet layer) ist dafür zuständig, den richtigen Zielrechner zu lokalisieren. Der Fachausdruck hierfür ist Routing und den haben Sie bestimmt schon mal gehört. In der Regel sind zwei Rechner nicht direkt miteinander verbunden, sondern nur indirekt (über einen 969
Netzwerkprogrammierung
oder mehrere Zwischenrechner) miteinander verknüpft. Damit eine Nachricht von Rechner R1 über R2 zu Rechner R3 gelangen kann, müssen die Netzwerkschichten von R1, R2 und R3 zusammenarbeiten: zuerst wird die Nachricht von R1 nach R2 geschickt und dann von R2 nach R3. IP
Das Protokoll, das in der Internet-Schicht zur Anwendung kommt, heißt IP (Internet Protocol) und ist ein verbindungsloser, unzuverlässiger Paketdienst. Was ist damit gemeint? Verbindungslos bedeutet, dass zwischen zwei Rechnern keine feste Verbindung (also keine Session) aufgebaut und für die Dauer der Kommunikation aufrechterhalten wird. Stattdessen wird der auszutauschende Datenstream in einzelne Pakete (packets) zerlegt, die unabhängig voneinander gesendet und beim Empfänger wieder gesammelt werden. Das IP-Protokoll kümmert sich dabei nicht darum, ob ein Paket verloren geht (und wird deshalb auch als unzuverlässig bezeichnet). Dies klingt zunächst schrecklich – Daten können verloren gehen! Aber schauen wir weiter.
TCP
Die darüber liegende Transportschicht kümmert sich darum, dass die IPPakete in der richtigen Reihenfolge ankommen und keines verloren geht (d.h., hier wird das Manko der unzuverlässigen Internet-Schicht wieder ausgeglichen). Das verwendete Protokoll zur Durchführung dieser Aufgaben ist in der Regel TCP (Transmission Control Protocol). TCP erkennt, wenn ein Paket verloren gegangen ist, und sorgt dann dafür, dass das Paket noch einmal gesendet wird.
UDP
Für die Transportschicht ist auch ein einfacheres Protokoll namens UDP (Universal Datagram Protocol) verfügbar, das auf die Fehlerbehebung von TCP verzichtet. Die Datenübertragung mit UDP ist daher schneller als mit TCP, aber dafür unzuverlässig. Für uns als Java-Programmierer ist die Transportschicht die erste wirklich interessante Schicht, da man erst ab hier ins Geschehen eingreifen kann durch die Wahl von TCP- oder UDP-basierten Verbindungen. Dies wird in den folgenden Abschnitten noch genauer beleuchtet. Die oberste Schicht ist schließlich die Anwendungsschicht (application layer), wo von einem Java-Programm aus eine Netzwerkverbindung aufgebaut wird. Eine solche Verbindung kann man in Form einer festen Verbindung als Bytestream (Stream-Sockets) realisieren oder aber als einzelne, voneinander unabhängige Nachrichten (Datagram-Sockets).
35.1.2 IP-Adressen, Ports und Domain-Namen Damit die Kommunikation zwischen Rechner gemäß dem oben beschriebenen TCP/IP-Stack funktioniert, fehlt noch ein wesentlicher Punkt, der bisher nicht erwähnt worden ist, nämlich die Adressierung. Wie können Rechner in einem Netzwerk eindeutig benannt werden, sodass eine Nachricht auch an den richtigen Empfänger geleitet wird?
970
Netzwerke
Die Lösung hierfür sind die sogenannten IP-Adressen. Es handelt sich dabei um eine eindeutige Zahl, die jeder Computer in einem IP-basierten Netzwerk zugewiesen bekommt. Hierfür existieren zwei Standards: ■ ■
IPv4 verwendet eine 32-Bit Zahl zur Darstellung der IP-Adresse IPv6 verwendet 128 Bit zur Darstellung
29
Der gebräuchliche Standard heutzutage ist noch IPv4 mit 32 Bit. Zur besseren Handlichkeit werden diese IP-Adressen häufig in Punktnotation als eine Folge von 4 Blöcken zu je einer 8 Bit-Zahl, jeweils getrennt durch einen Punkt, z. B. 204.160.241.98 notiert. Mit 8 Bit lassen sich Zahlen im Bereich von 0 bis 255 darstellen, und IP-Adressen stammen daher aus dem Wertebereich 0.0.0.0 bis 255.255.255.255 (wobei diese Grenzen selbst allerdings keine gültigen Adressen sind).
30 31
Die Vergabe von IP-Adressen erfolgt nicht willkürlich, sondern ist durch bestimmte internationale Institutionen genau geregelt, was aber im Rahmen dieser Einführung nicht weiter wichtig ist.
32
Interessanter ist aber, dass eine IP-Adresse logisch gesehen aus zwei Komponenten besteht: der Netzwerk-ID und der Host-ID. Da der IP-Adressraum in verschiedene Klassen eingeteilt ist, ist je nach Klasse die Anzahl der Bits, welche die Netzwerk-ID bzw. Host-ID bilden, unterschiedlich.
33 34
Die Klassenunterteilung ist hierarchisch gestaffelt von A bis D: ■ ■ ■ ■
35
Klasse A: 7-Bit Netzwerk-ID, 24-Bit Host-ID (Adressen 0.xxx.xxx.xxx bis 127.xxx.xxx.xxx) Klasse B: 14-Bit Netzwerk-ID, 16-Bit Host-ID (128.0.xxx.xxx bis 191.255.xxx.xxx) Klasse C: 21-Bit Netzwerk-ID, 8-Bit Host-ID (192.0.0.xxx bis 223.255.255.xxx) Klasse D: 28-Bit Host-ID (Adressen 224.0.0.1 bis 239.255.255.255)
36 37
Es gibt ferner noch die Klasse E, die aber in dieser Einführung nicht weiter interessant ist.
38
Die oberste Ebene bilden die Klasse-A Netze. Da bei ihnen 7 Bit für ihre eindeutige ID vorhanden sind, bedeutet dies, dass es nur maximal 27= 128 verschiedene Klasse-A Netze auf der Welt geben kann, die allerdings dann sehr viele Rechner enthalten können, da ja 24 Bit zur Codierung von entsprechenden Host-IDs zur Verfügung stehen, d.h., bis zu 224 (rund 16 Mio.) Rechner können zu einem Klasse-A Netz gehören.
39 40 41
Analog verhält es sich mit den anderen Klassen; wichtig ist insbesondere die Klasse C, da sie eine vertretbare Anzahl von Hosts aufnehmen kann (28 = 256) und es von dieser Klasse 221 = rund 2 Mio. verschiedene geben kann. Viele kleine Unternehmen beantragen daher ein Klasse-C Netz für ihr lokales Netzwerk. Einen Sonderfall stellt die Klasse D dar. Diese IP-Adressen werden für sogenannte Multicast-Nachrichten verwendet, bei denen eine Nachricht an mehrere IP-Adressen geschickt wird.
42
971
Netzwerkprogrammierung
Allerdings ist die gewählte Aufteilung der Klassen unglücklich. Mittlerweile haben viele Unternehmen mehr als 256 Rechner, sodass ein Klasse-C Netz nicht ausreicht und man gezwungen ist, ein Klasse-B Netz zu beantragen. Dies ist jedoch meistens zu viel des Guten, denn in einem solchen Netz könnte man ja bis zu 216 = 65536 Rechner betreiben, was für 95% aller Unternehmen viel zu viel ist. Die Konsequenz ist, dass die noch freien Klasse-B Netze sehr knapp geworden sind. Allerdings wird die Lage etwas dadurch entschärft, dass ein Netz intern noch einmal in Subnetze unterteilt werden kann und dadurch die Ausbeute an verfügbaren IP-Adressen verbessert wird. Mittelfristig wird man es dennoch nicht vermeiden können, auf größere IPAdressen mit 128 Bit Länge, also den IPv6 Standard zu migrieren. IPv6Adressen bestehen aus 8 Gruppen zu je 16 Bit, die in hexadezimaler Form notiert werden, z. B. 1080:0:0:0:8:800:200C:417A. Während IP-Adressen auf der Internet-Schicht angesiedelt sind, gibt es noch einen Adressierungszusatz, die Ports, die der Transportschicht und somit TCP/UDP zugeordnet sind. Ein Port ist einfach eine Nummer zwischen 0 und 65535 und dient zur weiteren Unterscheidung, welcher Dienst, der auf einem Rechner gerade läuft, angesprochen werden soll. Man kann IP-Adressen und Ports mit einem Telefonanschluss in einem großen Unternehmen vergleichen: es gibt die Hauptnummer, die der IP-Adresse entspricht, und dann hat jedes Büro eine eigene Durchwahl, die Nebenstelle, was ungefähr der Portnummer entspricht. Gewisse Portnummern sind reserviert (bei den meisten Betriebssystemen 0 bis 1023), und häufig als Quasi-Standard durch bestimmte Dienste belegt, z. B. wartet ein HTTP-Server in der Regel unter Port 80 auf Anfragen von Browsern und ein FTP-Server läuft unter Port 21. Sehr hilfreich ist der Umstand, dass man numerische IP-Adressen auf halbwegs verständliche Namen – die Domain-Namen – abbilden kann. Solche Domain-Namen haben den Vorteil, dass sie für Menschen viel besser zu behalten sind. Ein typischer Domain-Name eines Rechners ist z. B. obelix.mpi-sb.mpg.de, wobei obelix der eigentliche Rechnername (= hostname) ist und der Rest bildet den Domain-Namen. Die Vergabe von Domain-Namen erfolgt über bestimmte Institutionen, die Network Information Center. Dabei gibt es viele Regeln, wie Domain-Namen aufgebaut sind. An dieser Stelle ist lediglich interessant, dass DomainNamen hierarchisch in Form eines Baums aufgebaut sind (Abbildung 35.3). Jeder Ast im Baum, d. h. die Verknüpfung der Knotenlabels (getrennt durch Punkte) von der Wurzel bis zu einem Knoten in diesem Baum, ist ein gültiger Domain-Name.
972
Netzwerke
Abbildung 35.3: Domain-Namen
com
gov
edu
org
net
de
mpg
29 30
mpi-sb asterix
obelix
31
Die oberste Schicht in diesem Baum bilden die Top-Level-Domains, z.B. ■ ■ ■ ■ ■
32
com : für kommerzielle Einrichtungen, überwiegend in den USA mil : für die US-Armee gov : für die US-Regierung edu : für Schulen, Universitäten diverse Länderdomains wie de, us, fr, ru, usw.
33 34
Wenn Sie sich noch einmal das Beispiel obelix.mpi-sb.mpg.de anschauen, dann können Sie sehen, dass die Domain-Hierarchie umgekehrt zu lesen ist: zuerst kommt de, darunter mpg, dann mpi-sb und zuletzt kommt der eigentliche Rechnername (sein »Vorname« sozusagen).
35
Während Menschen mehr mit den einprägsamen Domain-Namen hantieren, will TCP/IP mit numerischen IP-Adressen arbeiten. Man braucht also eine Zuordnung von Domain-Namen auf IP-Adressen, das sogenannte Mapping. Diese Zuordnung erfolgt durch einen Dienst, den Domain Name Service (DNS). Es handelt sich dabei um eine weltweit verteilte Datenbank, die auf entsprechenden Servern (den Domain Name Servern) abgelegt ist. Wenn nun in einem Programm ein Benutzer einen Domain-Namen eingibt, kontaktiert das Programm, falls möglich, einen Domain Name Server und bittet um die Übersetzung des Domain-Namens in eine IP-Adresse. Wenn der Domain Name Server die Antwort nicht selber weiß, fragt er bei einem hierarchisch über ihm angeordneten Server an.
36
Häufig hat man bei der Netzwerkprogrammierung das Problem, dass der Rechner, auf dem man arbeitet, gerade keinen Netzwerkanschluss hat. Oder man entwickelt eine Anwendung, die erst einmal auf dem lokalen Rechner getestet werden soll, bevor sie auf die Rechner in einem Netzwerk installiert wird.
40
Aus diesem Grund wurde bei der Entwicklung des IP-Standards auch eine ganz besondere IP-Adresse reserviert, nämlich 127.0.0.1 (die loopbackAdresse). Sie bezeichnet immer den lokalen Rechner, auf dem ein Programm gerade läuft. Der dazu gehörende Rechnername ist dann der »localhost«.
42
37 38 39
41
973
Netzwerkprogrammierung
35.2 Socketverbindungen Sockets (wörtlich Steckdose) sind die Anschlusspunkte, die man auf Programmebene von einer Netzwerkverbindung zu sehen bekommt. Der eine Socket findet sich in dem Client-Programm, der andere Socket findet sich in dem Programm auf der Server-Seite wieder. Dazwischen gibt es ein unsichtbares »Kabel« (der TCP/IP-Stack), über das die gesamte Kommunikation läuft. Als Programmierer muss man sich dabei um die eigentliche Übertragung keine Gedanken mehr machen; man muss nur dafür sorgen, dass in die Sockets Daten hineingebracht bzw. ausgelesen werden. Wie schon kurz erwähnt, gibt es zwei Arten von Socketverbindungen: Stream-Sockets beruhen auf dem TCP-Protokoll und realisieren einen verbindungsorientierten, zuverlässigen Datenstream zwischen Client und Server. Datagram-Sockets beruhen auf dem UDP-Protokoll und realisieren eine verbindungslose, unzuverlässige Paketvermittlung zwischen Client und Server.
35.2.1 Adressierung mit InetAddress Bevor mit Sockets überhaupt gearbeitet werden kann, wird eine Möglichkeit benötigt, mit Internet-Adressen, also IP-Adressen und Domain-Namen, umzugehen. Hierfür gibt es in Java die Klasse InetAddress aus dem Paket java.net. Sie enthält sowohl die numerische IP-Adresse als auch den entsprechenden Domain-Namen. InetAddress kann dabei sowohl für den heutigen üblichen IPv4 als auch den zukünftigen IPv6 Standard eingesetzt werden1. Tabelle 35.1: Wichtige Methoden der Klasse InetAddress
Methode
Beschreibung
static InetAddress getByName( String host)
Erzeugt ein InetAddress-Objekt für die angegebene Adresse (host kann ein Domainname oder eine numerische IP-Adresse sein).
static InetAddress getByAddress( byte[] adr)
Erzeugt ein InetAddress-Objekt für die angegebene Adresse, wobei adr ein Array mit 4 (IPv4) oder 16 (IPv6) Bytes sein muss.
static InetAddress getLocalHost()
Erzeugt ein InetAddress-Objekt für den lokalen Rechner.
String getHostName()
Liefert den Domain-Namen der InetAddressInstanz.
1
974
Es existieren auch spezialisierte Unterklassen Inet4Address und Inet6Address für die beiden Standards. Sie werden im Rahmen dieses Buches aber nicht behandeln.
Socketverbindungen
Methode
Beschreibung
String getHostAddress()
Liefert die IP-Adresse in Punktnotation (z. B. 192.168.0.4).
byte[] getAddress()
Liefert die IP-Adresse als Array von 4 Byte (IPv4) bzw. 16 Byte (IPv6), wobei das höchstwertige Byte (das erste Byte von links) bei Index 0 abgelegt wird.
Tabelle 35.1: Wichtige Methoden der Klasse InetAddress (Forts.)
29
Das Anlegen und Initialisieren eines InetAddress-Objekts erfolgt über den Aufruf einer geeigneten statischen Methode der Klasse InetAddress (Tabele 35.1).
30
Listing 35.1: IP_Adresse.java – Ermittlung der IP-Adresse eines Rechners
31
import java.net.*;
32
class IP_Adresse { public static void main(String[] args) { System.out.println();
33
if(args.length != 1) { System.out.println(" Falsche Anzahl Argumente in Befehlszeile"); System.out.println(" Aufruf: java IP_Adresse "); System.exit(0); }
34 35
try { // von angegebenem Rechner InetAddress adresse = InetAddress.getByName(args[0]); System.out.println(" " + args[0] + " hat IP-Adresse " + adresse.getHostAddress()); } catch(UnknownHostException e) { System.err.println(" DNS-Lookup nicht möglich"); }
36 37
}
38
}
Aufruf: 39
C:\Beispiele\Kapitel35>java IP_Adresse localhost localhost hat IP-Adresse 127.0.0.1
Das Programm erwartet einen Hostnamen und erzeugt für diesen mithilfe der statischen Methode getByName() ein InetAddress-Objekt. Danach kann mit getHostAddress() eine String-Darstellung der IP-Adresse ausgegeben werden. Beim Instanziieren eines InetAddress-Objekts durch getByName() wird im Hintergrund ein DNS-Server kontaktiert, um die Übersetzung des Namens in eine IP-Adresse anzufordern. Dies erfordert natürlich, dass von Ihrem lokalen Rechner aus eine DNS-Abfrage möglich ist, also ein DNS-Server erreichbar ist. Bei Misserfolg wird ansonsten eine UnknownHostException geworfen.
40 41 DNS-Server verfügbar?
42
975
Netzwerkprogrammierung
Falls Sie den Namen Ihres Rechners nicht wissen: unter Windows finden Sie ihn in dem Eigenschaften-Dialog zum ARBEITSPLATZ, unter Linux geben Sie printenv ein und suchen nach HOSTNAME. Probieren Sie auf jeden Fall als Parameter auch »localhost« aus und schauen Sie, welche IP-Adresse gemeldet wird. Falls gerade kein Netzwerk zur Verfügung steht, können Sie sich auch mit Ihrem Modem ins Internet einloggen. Ihr Internet Service Provider weist Ihnen dann für die Dauer der Verbindung eine dynamische IP-Adresse zu, die Sie mit dem obigen Programm herausfinden können.
TIPP
Für lokale Tests, d. h. ohne Verbindung zu einem Netzwerk, können Sie sich eine gültige IP-Adresse mit InetAddress.getByName() anlegen, indem Sie als Parameter null, localhost2 oder die korrespondierende IP-Adresse 127.0.0.1 angeben: InetAddress adresse = InetAddress.getByName(null);
Seit dem JDK 1.4 ist noch eine weitere Klasse verfügbar, die speziell für den Einsatz im Verbund mit Kanälen3 (Channels) gedacht ist: InetSocketAddress. Der wesentliche Unterschied zu InetAddress liegt darin, dass man dem Konstruktor neben der IP-Adresse noch eine Portnummer mitgibt. Tabelle 35.2: Konstruktoren der Klasse InetSocketAddress
Methode
Beschreibung
InetSocketAddress(String host, int port)
Konstruktor. Erzeugt eine Instanz, die an host und port gebunden ist.
InetSocketAddress(int port)
Konstruktor. Erzeugt eine Instanz, die an die lokale IP-Adresse und port gebunden ist.
35.2.2 Stream-Sockets (TCP-Sockets) Stream-Sockets verwenden das TCP-Protokoll und realisieren einen verbindungsorientierten, zuverlässigen Datenstream zwischen einem Client und einem Server. Entsprechend dem Client-Server-Modell ist dabei der Client die aktive Seite und der Server bildet die passive Seite, d.h., der Client öffnet eine Socketverbindung zu einem Server, der nur darauf wartet, dass endlich jemand bei ihm anfragt. Sobald dies passiert ist, wird eine feste Verbindung zwischen den beiden Parteien aufgebaut und Daten werden in Form von IP-Paketen (also häppchenweise) ausgetauscht. Das TCP-Protokoll stellt dabei sicher, dass die Pakete in der richtigen Reihenfolge ankommen und keines verloren
2 3
976
Klappt nur, wenn localhost in dem speziellen Systemfile hosts konfiguriert ist. Unter Windows NT/2000/XP/Vista finden Sie diese Datei im Windows-Unterverzeichnis \system32\drivers\etc\. mehr dazu im Kapitel 31.1, Abschnitt »Verzeichnisse«
Socketverbindungen
geht bzw. bei Misserfolg dafür gesorgt wird, dass die betreffenden Pakete vom Sender erneut geschickt werden. Aus Programmiersicht besteht eine Socketverbindung vor allem aus den Endpunkten, den Sockets: einer auf dem Client und einer auf dem Server. Ein Socket wird dabei durch die Angabe einer IP-Adresse und einer Portnummer bestimmt, d.h., für eine Verbindung werden vier Werte benötigt: IP-Adresse und Portnummer des Clients sowie IP-Adresse und Portnummer des Servers. Die Java-Bibliothek stellt hierfür zwei besondere Klassen bereit: java.net.ServerSocket für den Socket auf dem Server sowie java.net.Socket für den Socket auf der Client-Seite.
29
Der prinzipielle Ablauf einer Stream-Socketverbindung sieht folgendermaßen aus:
31
1.
30
Der Server richtet sich darauf ein, dass Anfragen für eine Socketverbindung an ihn gerichtet werden. Hierzu legt er eine Instanz der Klasse ServerSocket an und weist ihr den gewünschten Port zu. Dann erfolgt der Aufruf der Methode accept(), der solange blockiert, bis ein Client eine Anfrage geschickt hat.
32
33
ServerSocket server = new ServerSocket(9000); // Port 9000 server.setSoTimeout(3000); // Timeout nach 3 s Socket serverSock = server.accept(); // blockiert hier, bis // Anfrage kommt oder // Timeout
2.
34 35
Falls man sich nicht sicher sein kann oder will, dass eine Anfrage auch tatsächlich eintrifft, kann man mit der Methode setSoTimeout() die Anzahl an Millisekunden festlegen, nach denen ein Timeout ausgelöst wird, d.h., der blockierende accept()-Aufruf wird mit einer SocketTimeoutException abgebrochen, wenn innerhalb der vorgegebenen Zeitdauer keine Anfrage zu diesem ServerSocket geschickt worden ist4. Ein Client initiiert mithilfe der Klasse Socket eine Socketverbindung zu einem Server. Er muss hierzu wissen, wo der Server läuft, also seine IPAdresse und die Portnummer kennen, auf der er lauscht, z. B.:
36 37 38
String Server_IP = "165.85.8.34"; Socket clientSock = new Socket(Server_IP, 9000);
39
3. Der Server wartet auf ankommende Anfragen in der Methode Server Socket.accept(), die nun die Client-Anfrage empfängt und ein neues Socket-Objekt anlegt. Die Socketverbindung ist damit aufgebaut. 4. Client und Server können nun über Input/Output-Streams miteinander kommunizieren. Jede Seite geht dabei identisch vor und legt jeweils einen InputStream (fürs Lesen) und einen OutputStream (fürs Schreiben) an. Für die obige Client-Seite also beispielsweise:
40 41 42
inStream = new BufferedReader( new InputStreamReader(clientSock.getInputStream())); 4
Wir werden später noch auf die Möglichkeit von nicht-blockierenden Socketverbindungen eingehen.
977
Netzwerkprogrammierung
outStream= new BufferedWriter(new OutputStreamWriter(clientSock.getOutputStream());
5. Daten werden mithilfe der entsprechenden Methoden (read(), write() o. ä.) der gewählten Streamklassen (hier BufferedReader und BufferedWriter) ausgetauscht. 6. Die Verbindung wird auf Server- und Clientseite geschlossen. clientSock.close(); // Clientseite serverSock.close(); // Serverseite
HALT
Eine leicht zu übersehende Falle bei TCP/IP-Verbindungen ist der folgende Umstand: Wenn auf Clientseite mit einer write()-Operation beispielsweise 100 Byte gesendet werden, ist nicht garantiert, dass man auf der Serverseite mit einem einzigen read()-Aufruf alle 100 Byte in einen Puffer wieder einlesen kann! Es könnte theoretisch passieren, dass der Server 100 Mal eine read()-Operation machen muss, bis er alle Daten empfangen hat. Für kleine Datenmengen stimmt die intuitive Annahme 1 x schreiben auf Clientseite entspricht 1 x lesen auf Serverseite zwar sehr oft, aber man kann sich eben nicht darauf verlassen. Nachdem Sie nun den grundlegenden Ablauf kennen, schreiten wir zur Tat und erstellen zwei kleine Programme, jeweils eines für Server und Client, um den Ablauf konkret nachzuvollziehen. Zunächst das Programm für den Server. Listing 35.2: ServerSocketDemo.java – Serverseite für eine Socketverbindung 01 import java.io.*; 02 import java.net.*; 03 04 public class ServerSocketDemo { 05 public static void main(String[] args) { 06 System.out.println(); 07 08 try { 09 // ServerSocket zum Lauschen auf Port 9000 anlegen 10 ServerSocket server = new ServerSocket(9000); 11 12 System.out.println(" Socket-Server: lausche auf Port 9000"); 13 14 // solange blockieren, bis eine Anfrage ankommt 15 Socket sock = server.accept(); 16 17 System.out.println(" Socket-Server: Verbindung aufgebaut"); 18 System.out.println(); 19 System.out.println(" Socket-Server: Socket-Parameter " 20 + sock); 21 System.out.println(); 22 23 // Input/Output Streams anlegen
978
Socketverbindungen
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 }
BufferedReader in = new BufferedReader( new InputStreamReader(sock.getInputStream())); BufferedWriter out = new BufferedWriter( new OutputStreamWriter(sock.getOutputStream()));
29
// die Nachricht vom Client lesen StringBuffer daten = new StringBuffer(); while(true) { String tmp = in.readLine();
30
if(tmp == null) break; else daten.append(tmp);
31
}
32
System.out.println(" Socket-Server: Nachricht von Client " + daten.toString());
33
// eine Nachricht an den Client senden out.write("Hallo Client, Deine Nachricht kam an!"); out.flush(); sock.shutdownOutput();
34 35
// Socket wieder schließen sock.close(); } catch(IOException e) { System.err.println(" Socket-Server: Fehler in " + "Socketverbindung!"); e.printStackTrace(); }
36 37
}
Die notwendigen Socket-Klassen finden sich im Paket java.net, das daher importiert werden muss (Zeile 2). Das eigentliche Starten des Servers findet in den Zeilen 10-15 statt, wo – wie schon besprochen – eine Instanz der Klasse ServerSocket angelegt und die Methode accept() aufgerufen wird, die auf Port 9000 lauscht. Wenn ein Client eine Socket-Anfrage zu Port 9000 schickt, liefert accept() eine Socket-Instanz zurück und der Server kann nun einen Ausgabestream (zum Schreiben an den Client) und einen Eingabestream (zum Lesen vom Client) anlegen. Für einfacheren und schnelleren Datentransfer ist es meistens besser, nicht die zugrunde liegenden InputStream/OutputStream-Klassen zu verwenden, sondern den Zugriff mit geeigneten Stream-Klassen zu wrappen, z. B. mit der Kombination BufferedReader/InputStreamReader fürs das Lesen und BufferedWriter/OutputStreamWriter für das Schreiben von Zeichen.
38 39 40 41 42
979
Netzwerkprogrammierung
TIPP
Für das Schreiben ist der Einsatz von PrintWriter statt BufferedWriter noch komfortabler, da man sich dann die flush()-Anweisungen sparen kann (erst bei Aufruf von flush() werden Daten wirklich physikalisch über die Leitung geschickt). Die restlichen Zeilen erzeugen einen einfachen Nachrichtenaustausch: Der Server liest eine Nachricht vom Client (Zeilen 31-43) und gibt sie aus. Gemäß der oben erwähnten Warnung kann man nicht davon ausgehen, dass der Server mit einem readLine()-Aufruf alle Daten gelesen hat. Darum muss das Lesen etwas umständlich in einer Endlosschleife erfolgen, die erst dann verlassen wird, wenn null als Ergebnis gelesen wird. Dies signalisiert das Ende des Eingabestreams. Der nächste Schritt besteht im Senden einer Antwort (Zeilen 45-48). Wichtig ist dabei der Aufruf von flush() zum Leeren von Zwischenpuffern, damit sichergestellt ist, dass alle Daten gesendet worden sind. shutdownOutput() beendet den Ausgabestream und sorgt dafür, dass dabei eine TCP-Endesequenz gesendet wird. Daran erkennt der Empfänger das Ende seines Eingabestreams. Schließlich wird der Socket mit close() geschlossen. Beachten Sie außerdem noch die Zeile 20, wo ein Socket-Objekt an eine println()-Anweisung übergeben wird. Dies bewirkt den impliziten Aufruf ihrer toString()-Methode und liefert als Ausgabe die Parameter der Socketverbindung, nämlich die verwendeten IP-Adressen und Portnummern. Dies ist zum Debugging und Nachvollziehen sehr hilfreich. Betrachten wir nun den Code des zugehörigen Client-Programms: Listing 35.3: ClientSocketDemo.java – Clientseite für eine Socketverbindung 01 import java.net.*; 02 import java.io.*; 03 04 public class ClientSocketDemo { 05 public static void main(String[] args) { 06 System.out.println(); 07 08 // Annahme: der Server läuft auf dem gleichen 09 // Rechner wie der Client und nutzt Port 9000 10 try { 11 InetAddress Server_IP = InetAddress.getByName(null); 12 13 // einen Socket anlegen 14 Socket clientSock = new Socket(Server_IP,9000); 15 16 System.out.println(" Socket-Client: Verbindung aufgebaut"); 17 System.out.println(); 18 System.out.println(" Socket-Client: Socket-Parameter: " 19 + clientSock); 20 System.out.println(); 21
980
Socketverbindungen
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 }
// Input/Output Stream anlegen BufferedReader in = new BufferedReader( new InputStreamReader(clientSock.getInputStream())); BufferedWriter out = new BufferedWriter( new OutputStreamWriter(clientSock.getOutputStream()));
29
// eine Nachricht senden out.write("Hallo Server, hier ist alles klar"); out.flush(); clientSock.shutdownOutput();
30
// die Antwort lesen StringBuffer daten = new StringBuffer();
31
while(true) { String tmp = in.readLine();
32
if(tmp == null) break; else daten.append(tmp);
33
}
34
String antwort = daten.toString(); System.out.println(" Socket-Client: Nachricht von Server: " + antwort); System.out.println();
35
// den Socket schließen clientSock.close();
36
} catch(ConnectException e) { System.err.println(" Socket-Client:" + e); e.printStackTrace(); } catch(IOException e) { System.err.println(" Socket-Client: " + e); e.printStackTrace(); }
37 38 39
}
Zeile 11 ermittelt die IP-Adresse, auf der der Server läuft. In diesem Beispiel wird davon ausgegangen, dass der Server und der Client auf dem gleichen Rechner laufen; deshalb wird als Argument für InetAddress einfach null (für »localhost«) übergeben. Nun wird es ernst: In Zeile 14 wird eine SocketInstanz angelegt, wobei als Konstruktorparameter die IP-Adresse des Servers und die Portnummer mitgegeben werden. Damit ist der Client in der Lage, über das TCP/IP-Protokoll diesen Server zu kontaktieren und die Socket-Verbindung aufzubauen.
40 41 42
Sobald die Verbindung steht, geht der Client auf die gleiche Weise vor wie der Server, d.h., er legt einen Ein- und einen Ausgabestream an, sendet an 981
Netzwerkprogrammierung
den Server per write() eine Nachricht und wartet dann auf die Antwort. Wie schon beim vorangegangenen Serverprogramm muss natürlich auch der Client mit flush() und shutdownOutput() sicherstellen, dass alle Daten sowie ein TCP-Endesignal geschickt werden (Zeilen 31 und 32). Das Lesen der Antwort in den Zeilen 34-44 erfolgt wie bereits beim Serverprogramm in einer Endlosschleife, die erst verlassen wird, wenn readLine() den Wert null zurückliefert, d.h., das Ende des Eingabestreams ist erreicht worden. Sicherlich sind Ihnen sowohl bei Server- als auch Clientprogramm die trycatch-Blöcke aufgefallen. Sie sind notwendig, weil beim Anlegen von Socketverbindung und Input/OutputStream verschiedene Exceptions geworfen werden können. Wichtig ist für die Client-Seite vor allem ConnectException, wenn keine Verbindung aufgebaut werden konnte. Nun sollten Sie Ihre erste Client-Server-Anwendung testen. Starten Sie in einem Konsolenfenster das Serverprogramm und in einem anderen Fenster den Client. Sie sollten die folgenden Ausgaben erhalten: Abbildung 35.4: Die Ausgabe des Clients
Abbildung 35.5: Die Ausgabe des Servers
Beachten Sie besonders die Ausgabe der Socketparameter von Server und Client. Der Server liefert für sein Socket-Objekt die Werte 127.0.0.1 und Port 1854 sowie localport 9000. Dies bedeutet, dass er zu einem Client bei der Adresse 127.0.0.1 an Port 1854 eine Verbindung aufgebaut hat, die lokal bei ihm mit Port 9000 spricht. Der Client erhält bei seinem Socket-Objekt als Ausgabe die gleiche IP, aber die Ports sind vertauscht, d.h., er hat eine Ver-
982
Socketverbindungen
bindung zur IP-Adresse 127.0.01. an Port 9000 aufgebaut, die mit dem lokalen Port 1854 spricht. Genau dies war unsere Absicht. Vielleicht fragen Sie sich, wo die ClientPortnummer 1854 herkommt. Die wurde ja nirgends festgelegt! Dies ist auch nicht nötig, da der Client beim Aufbau seiner Socketverbindung automatisch vom Betriebssystem eine freie lokale Portnummer zugewiesen bekommt.
29
Falls Sie ein Netzwerk zur Verfügung haben, probieren Sie unbedingt einen Client-Server-Lauf, bei dem Client und Server auf verschiedenen Rechnern laufen. Passen Sie dazu das Client-Programm entsprechend an, sodass die IP-Adresse als Kommandozeilenparameter erwartet wird. Achten Sie dann wieder auf die Ausgaben der Socketparameter.
30 31
Die nachfolgenden Tabellen zeigen eine Zusammenfassung der wichtigsten Methoden von Socket und ServerSocket. Methode
Beschreibung
ServerSocket(int port)
Konstruktor. Erzeugt eine Instanz, die auf port lauscht.
ServerSocket(int port, int backlog, InetAddress adr)
Konstruktor. Erzeugt eine Instanz, die an der Adresse adr (manche Server haben mehrere IP-Adressen) und port lauscht. backlog gibt an, wie viele gleichzeitige Client-Anfragen in einer Warteschlange gehalten werden können. Wird backlog überschritten, erhält ein anfragender Client sofort die Antwort »Connection refused«.
ServerSocketChannel getChannel()
Liefert den assoziierten Kanal (siehe Abschnitt 35.3).
Socket accept()
Blockiert, bis eine Anfrage auf dem Port empfangen wird, und liefert eine Socket-Instanz zurück.
void close()
Schließt den Socket.
void setSoTimeOut(int millis)
Setzt die Timeout-Zeit auf millis Millisekunden; accept() wird nach Ablauf der Timeout-Zeit mit einer InterruptedIOException unterbrochen.
Methode
Beschreibung
Socket(String host, int port)
Öffnet eine Socketverbindung zum angegebenen Server host und Portnummer port.
void close()
Schließt die Verbindung.
void setSoTimeOut(int millis)
Setzt den Timeout in Millisekunden. Nach Ablauf von millis ms wird eine read()-Operation auf dem Eingabestream mit SocketTimeoutException unterbrochen.
32 Tabelle 35.3: Wichtige Methoden der Klasse ServerSocket
33 34 35 36 37 38 39
Tabelle 35.4: Wichtige Methoden der Klasse Socket
40 41
int getPort()
42
Liefert den Port auf der Serverseite, zu dem die Socketverbindung besteht.
983
Netzwerkprogrammierung
Tabelle 35.4: Wichtige Methoden der Klasse Socket (Forts.)
Methode
Beschreibung
InputStream getInputStream()
Liefert einen Eingabestream zum Lesen von der Socketverbindung.
OutputStream getOutputStream()
Liefert einen Ausgabestream zum Schreiben.
void shutdownOutput()
Noch gepufferte Daten werden gesendet, gefolgt von einem TCP/IP-Endesignal.
35.2.3 Datagram-Sockets (UDP-Sockets) Datagram-Sockets verwenden nicht das TCP-Protokoll, sondern das einfachere UDP-Protokoll. Wie bei Stream-Sockets werden Daten in Form von IPPaketen ausgetauscht, allerdings wird keine feste Verbindung zwischen Sender und Empfänger aufgebaut, und Pakete, die verloren gehen, sind verloren! Es wird auch keine Garantie dafür übernommen, dass die Reihenfolge der gesendeten Pakete auch beim Empfänger noch die gleiche ist. Da der Overhead für die Überwachung und Korrektur des Paketstreams im Vergleich zu TCP wegfällt, ist eine Datagram-Socketverbindung in der Regel schneller, dafür aber halt unzuverlässig. Solche Verbindungen sind deshalb nur dann geeignet, wenn es vor allem auf eine schnelle Übertragung ankommt und es nicht besonders schlimm ist, wenn einige Pakete unterwegs verloren gehen. Ein Beispiel könnte z. B. die Übertragung von Audiodaten sein, die schnell geliefert werden müssen, wenn sie in Echtzeit abgespielt werden sollen. Falls mal einige Pakete verloren gehen, ist dies nicht weiter schlimm, da es vom menschlichen Ohr meist nicht wahrgenommen wird. Analog zu Stream-Sockets – repräsentiert durch ServerSocket und Socket – gibt es auch bei Datagram-Sockets eine spezielle Socket-Klasse, die passend DatagramSocket heißt. Ferner wird noch eine Klasse DatagramPacket für das zu sendende Datenpaket eingesetzt. Diese beiden Klassen werden sowohl vom Client als auch vom Server verwendet. Darüber hinaus wird nichts mehr benötigt; es gibt insbesondere kein Pendant zur Klasse ServerSocket. Tabelle 35.5 zeigt die grundlegenden Methoden von DatagramSocket. Tabelle 35.5: Wichtige Methoden der Klasse DatagramSocket
984
Methode
Beschreibung
DatagramSocket() DatagramSocket(int port)
Öffnet eine Socketverbindung zu einem beliebig gewählten Port auf dem lokalen Rechner bzw. zu port.
void close()
Schließt den Socket.
void receive(DatagramPacket p)
Blockiert so lange, bis ein Paket empfangen worden ist, und legt es in p ab.
Socketverbindungen
Methode
Beschreibung
void send(DatagramPacket p)
Sendet das Paket p.
void setSoTimeout(int millis)
Setzt die Timeout-Zeit auf millis ms. receive() wartet maximal millis ms und wird dann mit SocketTimeoutException unterbrochen.
Tabelle 35.5: Wichtige Methoden der Klasse DatagramSocket (Forts.)
29 Ein wesentlicher Unterschied zu den Stream-Sockets ist der Umstand, dass die Serveradresse nicht über die Socket-Klasse festgelegt wird, sondern jedem einzelnen Paket mitgegeben werden muss. Das Datenpaket selbst wird hierbei durch eine Instanz von DatagramPacket repräsentiert.
30 Tabelle 35.6: Wichtige Methoden der Klasse DatagramPacket
31
Methode
Beschreibung
DatagramPacket(byte[] b, int laenge)
Konstruktor. Erzeugt ein Paket zum Empfang von maximal laenge Byte, die in b abgelegt werden.
DatagramPacket(byte[] b, int lsenge, InnetAddress adr, int port)
Konstruktor. Erzeugt ein Paket zum Senden von laenge Byte aus dem Array b zur Adresse adr.
33
InetAddress getAddress()
Liefert die IP-Adresse des Senders bzw. Empfängers des Pakets.
34
byte[] getData()
Liefert das Array der Daten zurück.
int getPort()
Liefert die Portnummer, zu dem gesendet bzw. von dem empfangen wird.
void setData(byte[] b)
Legt das Array fest, woraus die zu sendenden Daten gelesen werden bzw. in das die empfangenen Daten geschrieben werden.
32
35 36 37
Der grundlegende Aufbau einer Datagram-Socketverbindung sieht folgendermaßen aus: 1.
38
Der Server erzeugt ein DatagramSocket-Objekt und bindet es an den gewünschten Port (z. B. 9000):
39
DatagramSocket serverSock = new DatagramSocket(9000);
2.
Es wird ein Puffer in Form eines Byte-Arrays angelegt sowie eine DatagramPacket-Instanz. In den Puffer werden später die empfangenen Daten hineingeschrieben.
40
int laenge = 100; byte[] puffer = new byte[laenge]; DatagramPacket empfangsPaket = new DatagramPacket(puffer,puffer.length);
41
3. Mit der receive()-Methode der Klasse DatagramSocket wird auf ein eingehendes Paket gewartet. Falls nicht endlos lange gewartet werden soll, kann mit der Methode setSoTimeout() eine maximale Dauer festgelegt
42
985
Netzwerkprogrammierung
werden, bei deren Überschreitung der receive()-Aufruf mit einer InterruptedIOException abgebrochen wird: serverSock.setSoTimeout(3000); // nach 3000 ms Timeout serverSock.receive(empfangsPaket);
4. Der Client geht analog vor: Zunächst wird ein DatagramSocket-Objekt erzeugt sowie ein Byte-Array mit den zu sendenden Daten. Außerdem muss die IP-Adresse des Empfängers (also des Servers) vorbereitet werden: DatagramSocket clientSock = new DatagramSocket(); InetAddress server_IP = InetAddress.getByName("obelix.muster.com"); int server_port = 9000; String nachricht = "Hallo Server, alles klar?"; byte[] puffer = nachricht.getBytes();
5. Nun wird das zu sendende Paket erzeugt. Da jedes Paket unabhängig von allen anderen verschickt wird, werden die Adressdaten (IPAdresse, Portnummer) im Paket selbst abgelegt: DatagramPacket sendPaket = new DatagramPacket(puffer,puffer.length, server_IP, server_port);
Wenn der Empfänger gleich bleibt, kann man zur Optimierung das einmal erzeugte Objekt weiterverwenden und allein die zu sendenden Daten durch Aufruf der Methode setData(byte[] puffer) ersetzen. 6. Schließlich sendet der Client das Paket mittels send(): clientSock.send(sendPaket);
7.
Der blockierende receive()-Aufruf auf Serverseite kehrt nun mit den empfangenen Daten im übergebenen DatagramPacket-Objekt zurück. Sie können jetzt mit getData() ausgelesen werden: String nachricht = new String(empfangsPaket.getData());
Der Server, d. h. der Empfänger, kann ferner über die Methoden getAddress() und getPort() von DatagramPacket die IP-Adresse und Portnummer des Senders abfragen. 8. Die Socketverbindung wird von Client und Server mit close() geschlossen: serverSock.close(); bzw. clientSock.close()
TIPP
TCP- und UDP-Portnummern haben nichts miteinander zu tun! Das heißt, man kann auf einem Server gleichzeitig einen TCP-Dienst und einen UDPDienst laufen lassen, die an die gleiche Portnummer gebunden sind. Die maximale Größe eines UDP-Pakets beträgt 64 Kbyte. Dies ist daher auch die maximale Größe, die man bei einem DatagramPacket als Größe für das Datenarray verwenden kann. Von der Maximalgröße muss man allerdings noch einige Bytes für Header-Informationen abziehen, sodass die Nutzgröße etwas geringer ist. Will man längere Pakete per Datagramm schicken, muss man die Daten auf mehrere Pakete verteilen und sich selbst darum kümmern, dass kein Paket verloren geht, die Reihenfolge beim Empfänger
986
Socketverbindungen
stimmt, etc. (Letztlich macht man also genau das, was TCP anbietet, weswegen man in der Regel gleich zu TCP und somit Stream-Sockets greift.) Schauen wir uns den Ablauf noch einmal in einem einfachen Client/ServerProgramm an. Zunächst das Listing für den Server: Listing 35.4: DatagramServer.java – Einsatz von DatagramSocket auf der Serverseite
29
01 import java.io.*; 02 import java.net.*; 03 04 public class DatagramServer { 05 public static void main(String[] args) { 06 System.out.println(); 07 08 try { 09 // Socket anlegen 10 DatagramSocket sock = new DatagramSocket(9000); 11 12 // Ein Paket anlegen, in das die empfangenen Daten 13 // gespeichert werden 14 int laenge = 100; 15 byte[] puffer = new byte[laenge]; 16 DatagramPacket paket = new DatagramPacket(puffer,laenge); 17 18 // auf eine Anfrage blockierend warten 19 System.out.println(" Server: warte auf Anfrage"); 20 sock.receive(paket); 21 22 // die Daten aus dem erhaltenen Paket entnehmen 23 String nachricht = new String(paket.getData()); 24 25 System.out.println(" Server: empfangen wurde: " 26 + nachricht); 27 28 // an den Client eine Bestätigung schicken 29 // die Herkunftsdaten stehen im empfangenen Paket 30 InetAddress client_IP = paket.getAddress(); 31 int client_port = paket.getPort(); 32 33 String antwort ="OK"; 34 puffer = antwort.getBytes(); 35 36 DatagramPacket paket2 = new DatagramPacket( 37 puffer,puffer.length,client_IP,client_port); 38 sock.send(paket2); 39 40 // Socket schließen 41 sock.close(); 42 } catch(SocketException e) { 43 System.err.println(" Server: Socket kann nicht " 44 + "angelegt werden"); 45 e.printStackTrace(); 46 } catch(IOException e) {
30 31 32
33 34 35 36 37 38 39 40 41 42
987
Netzwerkprogrammierung
47 48 49 50 } 51 }
System.err.println(" Server: I/O Fehler "); e.printStackTrace(); }
Richtig los geht es ab Zeile 10, wo ein DatagramSocket angelegt und an einen festen Port (9000) gebunden wird. Danach wird ein DatagramPacket angelegt und mit dem blockierenden receive() auf ein eingehendes Paket gewartet. Wenn ein Paket empfangen worden ist, wird darin nachgeschaut, woher es gekommen ist (Zeilen 30-31) und es wird dann in den Zeilen 33-38 ein Antwortpaket vorbereitet und abgeschickt. Analog zu Stream-Sockets werden alle Anweisungen zur Socketkommunikation mit try-catch abgesichert, um eventuell auftretende Exceptions abzufangen. Der Client macht im Prinzip das Gleiche wie der Server, nur in umgekehrter Reihenfolge: erst ein Paket senden und dann eins empfangen. Beachten Sie, dass der Client beim Anlegen eines DatagramSocket keine weiteren Parameter wie IP-Adresse des Servers oder Portnummer angeben muss, da diese Information bei einer UDP-Verbindung jedem einzelnen Paket mitgegeben wird. Listing 35.5: DatagramClient.java – Einsatz von DatagramSocket auf der Clientseite import java.io.*; import java.net.*; public class DatagramClient { public static void main(String[] args) { System.out.println(); try { // ein Socket-Objekt anlegen DatagramSocket sock = new DatagramSocket(); // Annahme: Server läuft auf dem gleichen Rechner wie // der Client InetAddress server_IP = InetAddress.getByName(null); int server_port = 9000;
// Ein Paket anlegen, in das die zu sendenden Daten // gespeichert werden String nachricht = "Hallo Server, alles klar?"; byte[] puffer = nachricht.getBytes(); DatagramPacket paket = new DatagramPacket(puffer,puffer.length, server_IP, server_port); // Paket senden sock.send(paket); // auf Antwort warten
988
Socketverbindungen
puffer = new byte[4]; // wir erwarten genau // 2 Buchstaben = 4 Bytes ('OK') DatagramPacket paket2 = new DatagramPacket(puffer, puffer.length); sock.receive(paket2); String antwort = new String(paket2.getData());
29
System.out.println(" Client: Server antwortete: " + antwort);
30
// Socket schließen sock.close(); } catch(IOException e) { System.err.println(" Client: IO Fehler"); e.printStackTrace(); }
31 32
} }
UDP-Pakete werden in vielen Netzwerken standardmäßig (von entsprechend konfigurierten Routern und Firewalls) herausgefiltert. In solchen Netzwerken können Sie daher keine Datagram-Sockets (und auch nicht die im folgenden Abschnitt vorgestellten Multicast-Sockets) verwenden.
33 TIPP
34 35
35.2.4 Multicast-Sockets Unter Multicasting versteht man das Versenden derselben Nachricht von einem Sender an mehrere Empfänger. In der IP-Welt heißt dies, dass Daten bzw. die zugrunde liegenden IP-Pakete an mehrere IP-Adressen gleichzeitig übermittelt werden. Logischerweise kann eine solche Übertragung nicht verbindungsorientiert sein und wird daher nicht per TCP, sondern durch das UDP-Protokoll realisiert. In Java erfolgt die Umsetzung mit der Klasse MulticastSocket, die eine Erweiterung von DatagramSocket darstellt.
36 37 38
Das Vorgehen ist dabei ähnlich wie beim Einsatz von DatagramSockets. Es werden jedoch noch zwei weitere Mechanismen benötigt: ■ ■
39
Group: Alle IP-Adressen, die bei einem Multicast angesprochen werden sollen, werden in einer Gruppe (Group) zusammengefasst. Time-to-live: Dies ist die Anzahl an Sprüngen (Hops), die ein Paket auf seiner Reise in den Computernetzwerken maximal machen kann. Bei jedem Weiterreichen von einem Rechner zum nächsten wird der Hopzähler dekrementiert. Wenn er null erreicht hat, wird das Paket vernichtet und nicht mehr weitergeleitet. Dadurch wird verhindert, dass Pakete ewig in den Netzwerken umherwandern.
40 41 42
989
Netzwerkprogrammierung
Das Vorgehen für einen Multicast sieht nun folgendermaßen aus: 1.
2.
Festlegen einer IP-Adresse und einer Portnummer für den Multicast. Die IP-Adresse muss bei IPv4 aus Klasse D sein, also zwischen 224.0.0.1 und 239.255.255.255 liegen (Achtung: 224.0.0.1 hat eine besondere Bedeutung: eine Nachricht an diese Adresse bewirkt, dass sie an alle Empfänger geht, die in irgendeiner Multicast-Gruppe eingetragen sind.) Noch eleganter ist natürlich die Verknüpfung der gewählten IP-Adresse mit einem Domain-Namen, der über einen Domain Name Server bekannt gemacht wird. Wenn ein Programm einen Multicast senden will, erzeugt es eine Instanz von MulticastSocket und trägt sich in die gewünschte Gruppe ein. Ferner sollte die Time-to-live angemessen gesetzt werden (abhängig von der Größe/Topologie des Netzwerkes): MulticastSocket serverSock = new MulticastSocket(port); serverSock.joinGroup(multicast_adresse); serverSock.setTimeToLive(50); // max. 50 Hops
3. Wie bei normalen DatagramSocket wird ein DatagramPacket erzeugt, mit Multicast-Adresse und dem Sendeport versehen, mit Nutzdaten gefüllt und per send() gesendet. byte[] puffer = nachricht.toString(); // nachricht sei ein String DatagramPacket paket = new DatagramPacket(puffer,puffer.length, multicast_adresse,port); serverSock.send(paket);
4. Auf der Empfängerseite wird ganz analog vorgegangen: Ein MulticastSocket zum gewünschten Port wird angelegt und der entsprechenden Multicast-Gruppe hinzugefügt. Dann wird mit receive() auf Nachrichten gewartet, die in einem bereitgestellten DatagramPacket abgelegt werden. MulticastSocket clientSock = new MulticastSocket(); clientSock.joinGroup(multicast_adresse); DatagramPacket empfangsPaket = new DatagramPacket(puffer, puffer.length); clientSock.receive();
Zum besseren Verständnis wieder ein einfaches Beispiel mit einem Server, der im Zwei-Sekunden-Takt das aktuelle Datum inkl. Uhrzeit als Multicast an interessierte Clients schickt. Zunächst werfen wir einen Blick auf den Server: Listing 35.6: MulticastServer.java – Multicast: Serverseite 01 02 03 04 05 06 07 08
990
import java.io.*; import java.net.*; import java.util.*; public class MulticastServer { public static void main(String[] args) { System.out.println();
Socketverbindungen
09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 }
if(args.length != 1) { System.out.println(" Falsche Anzahl Argumente "); System.out.println(" Aufruf mit "); System.exit(0); } try { // ein Socket-Objekt anlegen int port = 9000; MulticastSocket sock = new MulticastSocket(port);
29 30
InetAddress multicast_adresse = InetAddress.getByName(args[0]); sock.joinGroup(multicast_adresse);
31
while(true) { // die aktuelle im Zwei-Sekunden-Takt Uhrzeit/Datum // an die Clients schicken String nachricht = (new Date()).toString(); byte[] puffer = nachricht.getBytes();
32
33
DatagramPacket paket = new DatagramPacket(puffer, puffer.length, multicast_adresse, port); sock.send(paket);
34 35
System.out.println(" Server: Nachricht gesendet"); Thread.sleep(2000); // 2s warten } } catch(IOException e) { System.err.println(" Server: IO Fehler"); e.printStackTrace(); } catch(InterruptedException e) { System.err.println(" Interrupted sleep"); e.printStackTrace(); }
36 37 38
}
39
Die interessanten Dinge beginnen ab Zeile 18, wo eine Instanz von MulticastSocket angelegt, der Einfachheit halber an einen fest codierten Port (9000) gebunden und dann zur gewünschten Multicast-Gruppe hinzugefügt wird. Die restlichen Anweisungen unterscheiden sich nun nicht mehr von einem normalen Datagram-Einsatz: Es wird ein DatagramPacket angelegt (Zeile 30), das die gewünschten Daten sowie Zieladresse und Portnummer enthält. Die Nutzdaten bestehen in diesem Beispiel aus dem aktuellen Datum, das in Zeile 27 über ein Date-Objekt ermittelt wird. Nach dem Senden des Pakets wird eine kurze Pause eingelegt, und das Spiel beginnt von Neuem.
40 41 42
991
Netzwerkprogrammierung
Betrachten wir nun den Client: Listing 35.7: MulticastClient – Multicast: Clientseite import java.io.*; import java.net.*; public class MulticastClient { public static void main(String[] args) { System.out.println(); if(args.length != 1) { System.out.println(" Falsche Anzahl Argumente in Befehlszeile"); System.out.println(" Aufruf mit "); System.exit(0); } try { // ein Socket-Objekt anlegen MulticastSocket sock = new MulticastSocket(9000); InetAddress multicast_adresse = InetAddress.getByName(args[0]); sock.joinGroup(multicast_adresse); // auf die Nachricht warten byte[] puffer = new byte[100]; // wir sind großzügig DatagramPacket paket = new DatagramPacket(puffer, puffer.length); sock.receive(paket); // Nachricht ausgeben String nachricht = new String(paket.getData()); System.out.println(" Empfangene Nachricht: " + nachricht); // Socket schließen sock.close(); } catch(IOException e) { System.err.println(" Client: IO Fehler"); e.printStackTrace(); } } }
Die Vorgehensweise ist ganz analog zum Server und sollte Ihnen keine Rätsel aufgeben. Zuerst wird ein MulticastSocket angelegt und an die vereinbarte Portnummer 9000 gebunden. Dann wird der Socket der gewünschten Multicast-Gruppe hinzugefügt. Schließlich wird ein Empfangspaket vorbereitet, in das dann die Nachricht vom Datumsserver empfangen wird.
992
Non-Blocking I/O
Multicast basiert auf UDP-Datagrammen, die von vielen Firewalls und Routern standardmäßig weggefiltert werden. Wenn Sie also Probleme mit Multicast haben, fragen Sie Ihren zuständigen Netzwerkadministrator. Ferner kann es Kollisionen mit vorhandenen Multicast-Gruppen geben. Verwenden Sie für eigene Versuche nach Möglichkeit Adressen aus dem Bereich 224.0.1.27 bis 224.0.1.225.
TIPP
29
Beenden Sie den Server mit (Strg)+(C).
35.3 Non-Blocking I/O
30 TIPP
31
Seit dem JDK 1.4 ist Java auch in der Lage, auf Socketverbindungen nichtblockierende Ein-/Ausgabeoperationen durchzuführen (meistens als Nonblocking I/O bezeichnet). Dies betrifft im Wesentlichen das Manko, dass ein read()-Aufruf bei einer InputStream-Klasse so lange blockiert, bis er etwas aus dem Eingabestream lesen konnte, und sei es auch nur die Markierung für das Stream-Ende.
32
33
Dies bedeutet, dass ein Thread, der so kühn war, einen read()-Aufruf für eine bestehende Socketverbindung durchzuführen, von sich aus keine Möglichkeit mehr hat, das Warten abzubrechen. Falls aus irgendeinem Grund keine Daten ankommen, dann »hängt« der Thread, anstatt eventuell andere nützliche Aufgaben zu erledigen. Aber auch schon der Schritt davor, nämlich das Warten auf eine ankommende Socketanfrage mittels ServerSocket.accept() lässt den Thread blockieren und gibt ihm keine Möglichkeit mehr, sich aus eigener Kraft wieder freizumachen5.
34 35 36
Vor diesem Hintergrund wurde die Ankunft des neuen I/O-Pakets java.nio von vielen Entwicklern begrüßt. Gemäß dem NIO-Modell erfolgt die Kommunikation über Ein-/Ausgabekanäle6. Ein Kanal ist dabei letztlich nichts anderes als die Verbindung zwischen einem Datenpuffer vom Typ ByteBuffer und einem Endpunkt, dem Socket.
37 38
Ein wesentliches Hilfsmittel für die Durchführung von Non-Blocking I/O ist ein Selektor. Hierbei handelt es sich um ein Objekt, welches bei ihm registrierte Ein-/Ausgabekanäle daraufhin überwacht, ob Aktivitäten wie z. B. Schreib-/Leseoperationen zurzeit möglich sind. Der Einsatz eines Selektors erfolgt in Java in Form der Klasse Selector (Paket java.nio.channels). Man erzeugt einen Selektor und gibt dann durch den Aufruf einer register()Methode an, welche Kanäle der Selektor überwachen soll. Die Überwachung erfolgt dabei ereignisorientiert, d.h., man muss auch festlegen, auf welche Ereignisse (Keys) der Selektor reagieren soll.
39 40 41 42
5 6
Es sei denn, der Thread hat vor dem Aufruf von accept() über setSoTimeout() einen Timeout gesetzt. Falls Sie NIO noch nicht kennen, sollten Sie erst das Kapitel 31 zur Ein-/Ausgabe lesen, da wir an dieser Stelle Kanäle und Puffer nicht erläutern.
993
Netzwerkprogrammierung
Tabelle 35.7: Wichtige Methoden von Selector
Methode
Beschreibung
static Selector open()
Erzeugt ein Selector-Objekt.
int select() int select(long millis)
Wartet blockierend bzw. maximal millis Millisekunden, bis auf einem der registrierten Kanäle eine I/O-Operation möglich ist; Rückgabe ist die Anzahl der verfügbaren Keys.
Set selectedKeys()
Liefert die Menge der Keys, für die ein Kanal Bereitschaft gemeldet hat.
Ein zu überwachendes Ereignis wird dabei durch eine Instanz der Klasse SelectionKey dargestellt. Sie besitzt die Methode channel(), mit der sich der zugehörige Kanal, der bereit für eine Ein-/Ausgabeoperation ist, ermitteln lässt. Mit einem Selector-Objekt allein kann man allerdings noch nicht sonderlich viel anfangen. Von Interesse sind auch die Ein-/Ausgabekanäle für die eigentliche Socket-Kommunikation. Wie bei den bisher vorgestellten Sockets gibt es auch diesmal die Unterscheidung in die verbindungsorientierten Stream-Sockets (TCP/IP) sowie die verbindungslosen DatagramSockets (UDP).
35.3.1 Stream-Sockets Für den Einsatz von Stream-Sockets benötigt man für non-blocking I/O die Klassen ServerSocketChannel sowie SocketChannel (Paket java.nio.channels), beides Unterklassen der abstrakten Klasse SelectableChannel, sowie die bereits erwähnte Klasse Selector. Die Klasse ServerSocketChannel stellt hierbei in gewisser Weise das Pendant zur bisher besprochenen Klasse ServerSocket dar. Die grundsätzliche Vorgehensweise für den serverseitigen Aufbau einer nicht-blockierenden Socketverbindung sieht folgendermaßen aus: 1.
Einen I/O-Kanal als Instanz von ServerSocketChannel öffnen und an den gewünschten Port binden. 2. Einen Selektor erzeugen und den Kanal bei ihm registrieren. 3. Mit dem Selektor die registrierten Kanäle auf bestimmte Ereignisse (als keys bezeichnet) überwachen. Sie werden durch Instanzen der Klasse SelectionKey repräsentiert. 4. Wenn ein registriertes Ereignis eingetreten ist, kann für den betroffenen Kanal ein SocketChannel-Objekt erzeugt und auf die Socketverbindung zugegriffen werden. 5. Das Lesen oder Schreiben erfolgt dann über einen Puffer vom Typ ByteBuffer. All diese Schritte wollen wir nun an einem konkreten Beispiel nachvollziehen.
994
Non-Blocking I/O
Listing 35.8: ServerSocketNIO.java – Non-Blocking Socket für Serverseite 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
import import import import import
java.net.*; java.nio.*; java.nio.channels.*; java.io.*; java.util.*;
29
public class ServerSocketNIO { final static int MAX_LAENGE = 1024;
30
public static void main(String[] args) { System.out.println();
31
try { // ServerSocket zum Lauschen auf Port 9000 anlegen ServerSocketChannel kanal = ServerSocketChannel.open(); kanal.configureBlocking(false); ServerSocket serverSock = kanal.socket(); serverSock.bind(new InetSocketAddress(9000));
32
33
System.out.println(" Socket-Server: Socket-Parameter " + serverSock);
34
// Puffer anlegen ByteBuffer puffer = ByteBuffer.allocate(MAX_LAENGE);
35 Selector selektor = Selector.open(); kanal.register(selektor, SelectionKey.OP_ACCEPT);
36
// blockierend warten selektor.select(); Set auswahl = selektor.selectedKeys();
37
Iterator it = auswahl.iterator();
38 SelectionKey key = null; if(it.hasNext()) key = (SelectionKey) it.next();
39
if(key == null) System.exit(0);
40 41
ServerSocketChannel aktKanal = (ServerSocketChannel) key.channel(); SocketChannel aktSocket = aktKanal.accept();
42 while(true) { int num = aktSocket.read(puffer); if(num == -1)
995
Netzwerkprogrammierung
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 } 77 }
break; }
int anzahl = puffer.position(); puffer.flip(); byte[] daten = puffer.array(); String nachricht = new String(daten,0,anzahl); System.out.println(" Socket-Server: Nachricht von Client: " + nachricht); // eine Nachricht an den Client senden String antwort = "Hallo Client, Deine Nachricht kam an!"; puffer = ByteBuffer.wrap(antwort.getBytes()); aktSocket.write(puffer); // Socket wieder schließen aktSocket.close(); aktKanal.close(); } catch(IOException e) { System.err.println(" Socket-Server: Fehler!"); e.printStackTrace(); }
In den Zeilen 14-24 werden zunächst die nötigen Vorbereitungen getroffen. Die statische Methode ServerSocketChannel.open() eröffnet einen Kanal und versetzt ihn mit seiner Methode configureBlocking() in den Zustand false, was für nicht-blockierend steht. Nun muss noch festgelegt werden, zu welchen Port der Socket verbunden werden soll. Dies kann erstaunlicherweise nicht über die Klasse ServerSocketChannel selbst erfolgen. Stattdessen muss man mit socket() ein ServerSocket-Objekt beschaffen und dessen bind()-Methode aufrufen. Diese Methode erwartet leider nicht einfach eine Portnummer, sondern immer eine InetSocketAddress-Instanz. Nach dem Anlegen eines Puffers in Zeile 24 kommt die bereits erwähnte Selector-Klasse zum Einsatz. Eine Instanz lässt sich nur über die statische Methode Selector.open() anlegen, bei der wir dann mithilfe von register() unseren erzeugten Kanal für das Ereignis SelectionKey.OP_ACCEPT, also die Annahme einer Socketverbindung, anmelden. Weitere Ereignistypen sind OP_READ (Daten zum Lesen vorhanden), OP_WRITE (Daten zum Schreiben vorhanden) und OP_CONNECT (eingehende Anfrage für Socketverbindung). Es handelt sich dabei um numerische Konstanten, die man auch addieren kann, um somit einen Kanal für verschiedene Ereignisse zu registrieren. Eine Registrierung für mehrere Ereignisse kann über mehrere register()-Aufrufe erfolgen oder aber auch durch eine ODER-Verknüpfung der Konstanten, also beispielsweise kanal.register(selektor, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
996
Non-Blocking I/O
In Zeile 31 startet der Selektor mit seiner select()-Methode nun den Überwachungsvorgang. Der Aufruf dieser Methode ist blockierend und wartet so lange, bis ein Kanal bereit ist (es existiert auch eine select(long)-Methode für das Warten mit Timeout). Das Ergebnis von select() ist eine Menge (Schnittstelle Set) an Ereignissen, die eingetreten sind, oder in der Sprache von Java-NIO: die ausgewählten Keys, die durch die Methode selectedKeys() in Zeile 32 ermittelt werden. In unserem Beispiel kann dies immer nur ein Key sein, daher ist die Iteration über die Menge in den Zeilen 34-42 eigentlich überflüssig und soll lediglich das typische Vorgehen aufzeigen, wenn mehrere Kanäle überwacht werden. Dies gilt auch für den Aufruf von key.channel() in Zeile 46, da wir nur einen Kanal registriert haben und den Kanal, der vom Selektor als bereit gemeldet wird, somit schon kennen. Jetzt kommt die Klasse SocketChannel ins Spiel. Wir rufen für den Kanal in Zeile 47 die Methode accept() auf und nehmen so die Socketverbindung an, die ein Client an den Server gesendet hat. Das Lesen über die Verbindung erfolgt wie im Kapitel 31.4, Abschnitt »Kanäle und Puffer« erläutert mit der read()-Methode des SocketChannel-Objekts und einem ByteBuffer. Die restlichen Zeilen bringen dann nichts Neues: Wir geben den Pufferinhalt aus und senden eine Antwort zurück, um dann die Socketverbindung wieder zu schließen.
29 30 31 32
33 34
Tabelle 35.8 und Tabelle 35.9 zeigen eine Zusammenfassung der wichtigsten Methoden von ServerSocketChannel und SocketChannel. Tabelle 35.8: Wichtige Methoden der Klasse ServerSocketChannel
35
Methode
Beschreibung
static ServerSocketChannel open()
Erzeugt ein neues Objekt, das noch an keinen Port gebunden ist.
SocketChannel accept()
Blockiert so lange, bis eine Socketverbindung aufgebaut ist, bzw. im nicht-blockierenden Modus kehrt der Aufruf ggf. mit null zurück.
37
void close()
Schließt den Kanal.
38
SelectableChannel configureBlocking( boolean modus)
Legt fest, ob der Kanal im blockierenden (modus gleich true) oder nicht-blockierenden Modus (false) laufen soll.
ServerSocketChannel open()
Öffnet den Kanal.
ServerSocket socket()
Liefert ein mit dem Kanal assoziiertes Server Socket-Objekt.
36
39 40 41 42
997
Netzwerkprogrammierung
Tabelle 35.9: Wichtige Methoden der Klasse SocketChannel
Methode
Beschreibung
SelectableChannel configureBlocking( boolean modus)
Legt fest, ob die Kommunikation im blockierenden (modus gleich true) oder nicht-blockierenden Modus (false) laufen soll.
int read(ByteBuffer puffer)
Liest so viele Byte, wie gerade verfügbar sind, in puffer ein; Rückgabewert ist die Anzahl der gelesenen Bytes oder –1 bei Stream-Ende; im nichtblockierenden Modus kehrt der Aufruf sofort zurück, wenn keine Daten vorliegen.
int write(ByteBuffer puffer)
Schreibt den Inhalt von puffer und gibt die Anzahl der tatsächlich geschriebenen Bytes zurück.
35.3.2 Datagram-Sockets Auch für die paketorientierten Datagram-Sockets haben wir das gleiche Problem, dass die Methode DatagramSocket.receive() solange blockiert, bis ein Paket empfangen worden ist. Allerdings ist die Situation etwas entspannter, da man mithilfe des Timeout-Mechanismus (via setSoTimeout()) eine nicht-blockierende Operation zustande bringen kann, wenn auch etwas ineffizient und nicht sehr elegant. Abhilfe schafft DatagramChannel, das Analogon zur oben besprochenen SocketChannel-Klasse. Ein Gegenstück zu ServerSocketChannel gibt es nicht. Die Durchführung von non-blocking I/O für Datagram-Sockets ist ähnlich wie im oben gezeigten Vorgehen für normale verbindungsorientierte Sockets. Der wesentliche Unterschied liegt daran, dass man immer die Klasse DatagramChannel einsetzt, anstatt wie bei Stream-Sockets mit zwei Klassen ServerSocketChannel und SocketChannel zu hantieren. Betrachten wir den folgenden Code für das prinzipielle Vorgehen auf der Serverseite: Listing 35.9: Aus DatagramServerNIO.java – Serverseite für eine Datagram-Verbindung 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17
998
DatagramChannel kanal = DatagramChannel.open(); kanal.configureBlocking(false); DatagramSocket sock = kanal.socket(); sock.bind(new InetSocketAddress(9000)); Selector selektor = Selector.open(); kanal.register(selektor, SelectionKey.OP_READ); // Ein Paket anlegen, in das die empfangenen Daten // gespeichert werden ByteBuffer puffer = ByteBuffer.allocate(MAX_LÄNGE); selektor.select(); Set auswahl = selektor.selectedKeys(); Iterator it = auswahl.iterator();
Non-Blocking I/O
18 19 20 21 22 23 24 25 26 27 28 29
DatagramChannel aktKanal = null; SocketAddress client = null; SelectionKey key = null; if(it.hasNext()) key = (SelectionKey) it.next(); if(key == null) System.exit(0);
29
aktKanal = (DatagramChannel) key.channel(); client = aktKanal.receive(puffer);
30
Die Zeilen 1-16 sind fast identisch zum vorangegangenen Beispiel für verbindungsorientierte Stream-Sockets. Mit der statischen DatagramChannel.open()-Methode wird der Ein-/Ausgabekanal geöffnet und in Zeile 4 an den gewünschten Port gebunden, wobei es nicht überraschen dürfte, dass DatagramSocket für die Socketverbindung eingesetzt wird. In den Zeilen 6 und 7 wird wieder ein Selector-Objekt zur Überwachung des Kanals erzeugt, wobei man hier bei der Auswahl der interessierenden Operationen nur OP_READ (Lesen) und OP_WRITE (Schreiben) zur Auswahl hat, da die beiden übrigen Keys OP_ACCEPT und OP_CONNECT bei den verbindungslosen Datagram-Sockets keinen Sinn machen. Davon abgesehen erfolgt der Umgang mit dem Selektor völlig identisch. Zum eigentlichen Lesen kommt es in den Zeilen 28-29. Wir ermitteln mit der channel()-Methode den zuständigen Kanal des ausgewählten SelectionKey und können dann die bereitstehenden Daten in einen ByteBuffer einlesen. Die verwendete Leseoperation receive() in Zeile 29 liefert dabei als Rückgabewert die Adresse des Senders zurück, sodass man mit dieser Information gegebenenfalls eine Antwort senden kann.
31 32
33 34 35 36 37
Auf der Buch-CD finden Sie im zugehörigen Kapitelverzeichnis das vollständige Beispiel. DISC
Methode
Beschreibung
static DatagramChannel open()
Öffnet den Kanal.
DatagramChannel connect( InetSocketAddress adr)
Lesen und Senden von Datagrammen erfolgt zu adr.
DatagramSocket socket()
Liefert einen Socket, der mit diesem Kanal assoziiert ist.
SelectableChannel configureBlocking( boolean modus)
Legt fest, ob die Kommunikation im blockierenden (modus = true) oder nicht-blockierenden Modus (false) laufen soll.
int read(ByteBuffer puffer)
Liest ein Datagramm in puffer ein; Rückgabewert ist die Anzahl der gelesenen Bytes oder –1 bei Stream-Ende; im nicht-blockierenden Modus kehrt der Aufruf sofort zurück, wenn keine Daten vorliegen.
38
Tabelle 35.10: Wichtige Methoden der Klasse DatagramChannel
39 40 41 42
999
Netzwerkprogrammierung
Tabelle 35.10: Wichtige Methoden der Klasse DatagramChannel (Forts.)
1000
Methode
Beschreibung
int write(ByteBuffer puffer)
Schreibt den Inhalt von puffer als Datagramm und gibt die Anzahl der tatsächlich geschriebenen Bytes zurück.
InetSocketAddress receive( ByteBuffer puffer)
Wie read(), gibt jedoch die Socket-Adresse der Gegenseite zurück.
int send(ByteBuffer puffer, InetSocketAddress adr)
Wie write(), sendet jedoch an adr und nicht an den eingestellten Empfänger.
Inhalt
36 HTTP-Verbindungen 29 30
Im vorherigen Kapitel haben Sie die Grundbausteine der Netzwerkkommunikation kennengelernt und sind nun mental dafür gerüstet, einen Blick auf das mittlerweile wichtigste Protokoll für den Nachrichtenaustausch zwischen Computern zu werfen: das Hypertext-Transfer-Protocol (HTTP).
31
HTTP ist das Protokoll, das für das World Wide Web (WWW) bzw. schlicht das Internet kreiert worden ist, um Webseiten im HTML-Format zwischen Computern auszutauschen. Technisch gesehen basiert eine HTTP-Verbindung auf den Ihnen mittlerweile bekannten Stream-Sockets und TCP/IP, sodass Sie im Prinzip mit den gezeigten Klassen bereits HTTP-Verbindungen realisieren könnten. Um Sie jedoch vom Low-Level-Programmieren mit Sockets zu entlasten, bietet Java im Package java.net bereits fertige Klassen.
32 33 34 35
36.1 Uniform Resource Locator (URL)
36
Ein URL dient zur eindeutigen Identifikation bzw. Adressierung einer Seite im World Wide Web und ist eine Subvariante des allgemeineren Uniform Resource Identifier (URI) und spezifiziert: ■ ■ ■ ■ ■
37
das verwendete Protokoll (meistens HTTP), den Domain-Namen des Servers, den anzusprechenden Port (optional; Standard ist 80 für HTTP, 21 für FTP), den Pfad bzw. Namen der gewünschten Datei (optional), einen Anker (eine Referenz) innerhalb der Datei (optional), markiert durch #.
38 39 40
Die generische Syntax eines URL ist daher Protokoll://Hostname:Port/Pfad/Dateiname#Referenz
41
Wie man an der Auflistung sehen kann, sind einige Komponenten optional. Im einfachsten Fall besteht daher ein URL aus dem Protokoll und dem Servernamen, z. B. http://www.suse.de, ein komplexer URL wäre z. B. http://www.spiegel.de:8080/Archiv/artikel_24.html#Anfang.
1001
Index
42
HTTP-Verbindungen
Wenn kein Dateiname in dem URL spezifiziert wird, dann wird der angesprochene Server automatisch nach einem Dokument mit dem Namen index.htm oder index.html suchen, evtl. auch nach anderen voreingestellten Namen, z. B. start.html oder default.html. In Java steht für den Umgang mit URLs die Klasse URL mit einer Reihe von Konstruktoren zur Verfügung, wobei der wichtigste und einfachste einen URL als String erwartet und daraus ein URL-Objekt erzeugt: try { URL meinUrl = new URL("http://www.mut.de"); } catch(MalformedURLException e) { }
Wenn der Konstruktor kein Objekt erzeugen kann, da die Angaben falsch oder unvollständig sind, wird eine MalformedURLException geworfen. Die Klasse URL bietet einige nette Methoden an, um alle notwendigen Informationen aus einem URL zu extrahieren; nennenswert sind: Tabelle 36.1: Wichtige Methoden der Klasse URL
Methode
Funktionalität
String getProtocol();
Liefert den Namen des verwendeten Protokolls (HTTP, FTP, ...).
String getHost();
Liefert den Hostnamen des Servers.
String getPort();
Gibt die in dem URL definierte Portnummer zurück.
String getPath();
Liefert den Pfad des URL.
String getFile();
Liefert den Dateinamen.
String getRef();
Liefert den Anker (Referenz).
InputStream openStream();
Liefert einen InputStream zum Lesen des Dateiinhaltes.
URLConnection openConnection() throws IOException
Liefert eine URLConnection zum Server des URL.
Neben diesen Metainformationen liegt natürlich das Hauptinteresse auf dem Inhalt der durch den URL adressierten Datei. Hierfür bietet die URLKlasse die Methode openStream(). Sie liefert direkt einen InputStream zurück, der an einen InputStreamReader weitergereicht werden kann. Man hat hierbei nur einen rein lesenden Zugriff und kann damit keine Daten an den kontaktierten Server senden. Dies werden Sie im übernächsten Abschnitt sehen. Das folgende Beispiel demonstriert den Umgang mit der URL-Klasse: Es wird ein URL angelegt, und die URL-Informationen inklusive Inhalt der angegebenen Webseite zeilenweise ausgegeben.
1002
Uniform Resource Locator (URL)
Um eine Webseite aus dem Internet lesen zu können, müssen Sie zuvor eine Verbindung zum Internet herstellen. Webseiten von einem lokalen oder einem Intranet-Server können selbstverständlich auch ohne Verbindung zum Internet abgefragt werden.
TIPP
Listing 36.1: URLInfo.java – URL-Informationen abfragen
29
import java.net.*; import java.io.*;
30
public class URLInfo { public static void main(String[] args) { System.out.println();
31
if(args.length != 1) { System.out.println(" Falsche Anzahl Argumente in Befehlszeile"); System.out.println(" Aufruf: java URLInfo "); System.exit(0); }
32
33
// URL anlegen try { URL url = new URL(args[0]); // Infos ausgeben System.out.println(" System.out.println(" System.out.println(" System.out.println(" System.out.println("
URL : Protokoll : Hostadresse: Portnummer : Dateiname :
System.out.println(" Dateiinhalt
34 " " " " "
+ + + + +
35
url.toString()); url.getProtocol()); url.getHost()); url.getPort()); url.getFile());
36
:");
37
BufferedReader input = new BufferedReader( new InputStreamReader(url.openStream()));
38
String zeile;
39
// den Inhalt des Dokuments zeilenweise lesen while ((zeile = input.readLine()) != null) System.out.println(zeile);
40 input.close(); } catch(MalformedURLException e) { System.err.println(" Ungueltiger URL!"); System.exit(0); } catch(IOException e) { System.err.println(" IOException!"); System.exit(0); }
41 42
} }
1003
HTTP-Verbindungen
36.2 URL mit Sonderzeichen: x-www-formurlencoded Wie Sie gesehen haben, kann ein URL auch Dateinamen enthalten, wodurch sich ein auf den ersten Blick nicht offensichtliches Portabilitätsproblem ergeben kann, da verschiedene Betriebssysteme auch teilweise unterschiedliche Konventionen haben bezüglich der Zeichen, aus denen ein Datei- bzw. Pfadname bestehen kann (z. B. welche Sonderzeichen wie &, %, $ dürfen verwendet werden?). Um solchen Problemen aus dem Weg zu gehen, wurde ein besonderes Format x-www-form-urlencoded definiert. Hierfür bietet Java die Klassen URLEncoder und URLDecoder mit den statischen Methoden static String URLEncoder.encode(String str, throws static String URLDecoder.decode(String str, throws
String enc) UnsupportedEncodingException String enc) UnsupportedEncodingException
an, die einen String erwarten sowie die gewünschte Zeichensatzcodierung, um den String in eine portable Form umzuwandeln bzw. wieder zurückzukonvertieren. Das WWW-Konsortium empfiehlt dabei »UTF-8« als Zeichensatzcodierung. Bei der Konvertierung werden die Buchstaben A-Z und die Ziffern 0-9 unverändert übernommen, während Leerzeichen durch »+« und Sonderzeichen durch ein »%xy« ersetzt werden, wobei xy den hexadezimalen Code des Sonderzeichens darstellt, z. B. String alt ="http://mein.server.de/band 12 Kap-C&A.html"; String neu = URLEncoder.encode(alt,"UTF-8");
Der String neu hat nun den Wert http%3A%2F%2Fmein.server.de%2Fband+12+Kap-C%26A.html
und kann als Argument für die Klasse URL verwendet werden. Der Einsatz von URLEncoder wird ferner auch dann notwendig, wenn Daten an einen Webserver geschickt werden sollen (z. B. Sucheingaben, Formulardaten), der sie in dieser codierten Form erwartet. Solche Daten können bei einer GET-Anfrage (siehe Abschnitt 36.4) an den eigentlichen URL in x-www-form-urlencoded-Format angehängt werden (z. B. www.google.de/ search?q=dirk+louis+jbuilder).
36.3 HTTP-Sessions durchführen Wenn man nicht nur einfach einen Server kontaktieren und den Inhalt einer spezifizierten Datei herunterladen will, sondern auch Daten an den Server senden möchte, dann muss man ein klein wenig mehr Aufwand treiben, als nur eine Instanz von URL einzusetzen. Man muss dann die Klasse URLConnection bzw. (für HTTP) HttpURLConnection einsetzen.
1004
HTTP-Sessions durchführen
Hierzu bietet die URL-Klasse eine spezielle Methode an: URLConnection openConnection() throws IOException openConnection() versucht, (über einen Stream-Socket) eine Netzwerkverbindung zu dem Server aufzubauen, der zu dem URL gehört, und gibt bei Erfolg eine Instanz von URLConnection zurück, genauer gesagt, je nach verwendetem und unterstütztem Protokoll eine davon abgeleitete Klasse (bei HTTP beispielsweise wird eine HttpURLConnection zurückgeliefert).
29 30
Lesender Zugriff (Empfangen) URLConnection stellt für den lesenden Zugriff die folgenden Methoden bereit: Methode
Beschreibung
InputStream getInputStream() throws IOException
Kreiert einen InputStream zum Lesen.
void setDoInput(boolean)
Verbindung für Lesen aktivieren (mit Parameter true)
31 Tabelle 36.2: Wichtige Methoden von URLConnection zum Lesen
Mit der Methode getInputStream() kann man einen I/O-Stream anfordern und diesen wie im vorherigen Abschnitt verwenden, um Daten zu lesen. Der Aufruf von setDoInput() ist dabei normalerweise nicht erforderlich, da jede URLConnection auf Lesen voreingestellt ist.
32
33 34 35
Listing 36.2: Aufbau einer lesenden Verbindung mit URLConnection try { URLConnection verbindung = meinUrl.openConnection(); BufferedReader input = new BufferedReader( new InputStreamReader(verbindung.getInputStream())); String zeile;
36
while ((zeile = input.readLine()) != null) System.out.println(zeile); } catch(IOException e) { // openConnection() hat nicht geklappt }
38
37
39
Schreibender Zugriff (Senden)
40
Das Senden von Daten an einen Server erfolgt ganz analog über einen OutputStream, den die Klasse URLConnection bereitstellt:
41 Methode
Beschreibung
OutputStream getOutputStream() throws IOException
Liefert einen OutputStream zum Schreiben an den Server.
void setDoOutput(boolean)
Verbindung für Schreiben aktivieren mit Parameter true.
Tabelle 36.3: Wichtige Methoden von URLConnection zum Schreiben
1005
42
HTTP-Verbindungen
Im Gegensatz zum Lesen ist die Voreinstellung für das Schreiben immer false, d.h., man muss zuerst setDoOutput(true) aufrufen. Natürlich ist eine weitere Voraussetzung, dass der angesprochene URL auch das Schreiben erlaubt. Falls dies nicht der Fall ist, wird getOutputStream() scheitern und eine Unterklasse von IOException (nämlich UnknownServiceException) werfen. Die Firma Sun bot früher zum Testen ein CGI-Script im Internet an (java.sun.com/cgi-bin/backwards), der einen String erwartete (in mit URLEncoder codierter Form) und ihn zusammen mit einem Antworttext in umgekehrter Reihenfolge (reversed) zurücklieferte. Da dieses Script derzeit nicht mehr zur Verfügung steht, haben wir als Ersatz auf unserer Website unter www.carpelibrum.de\test\java\ ein eigenes PHP-Script namens backwards.php eingerichtet. Das folgende Beispiel zeigt, wie eine HTTP-Verbindung zu diesem Script aufgebaut wird, ein String geschickt (geschrieben) wird und schließlich die Antwort wieder gelesen und ausgegeben wird. Falls Sie über ein Firmennetzwerk ins Internet gehen, beachten Sie bitte auch den Abschnitt 36.5. Listing 36.3: HTTP_reverse.java – Senden und Lesen mit der Klasse URLConnection import java.net.*; import java.io.*; class HTTP_reverse { public static void main(String[] args) { System.out.println(); if(args.length != 1) { System.out.println(" Falsche Anzahl Argumente in Befehlszeile"); System.out.println(" Aufruf mit "); System.exit(0); } try { String nachricht = URLEncoder.encode(args[0], "UTF-8"); URL url = new URL("http://www.carpelibrum.de/test/java/backwards.php"); // die Verbindung aufbauen URLConnection verbindung = url.openConnection(); verbindung.setDoOutput(true); // den String schreiben PrintWriter output = new PrintWriter(verbindung.getOutputStream()); output.println("string=" + nachricht); output.close(); // die Antwort lesen BufferedReader input = new BufferedReader(new
1006
Das HTTP-Protokoll
InputStreamReader(verbindung.getInputStream())); String antwort; while( (antwort = input.readLine()) != null) System.out.println(antwort); input.close(); } catch(IOException e) { System.err.println(" " + e.getMessage()); }
29 30
} }
Ausführung:
31
C:\Beispiele\Kapitel36>java HTTP_reverse BART Die Umkehrung von 'BART' ist: 'TRAB'
32
C:\Beispiele\Kapitel36>java HTTP_reverse "030 123"
33
Die Umkehrung von '030 1234' ist: '4321 030' C:\Beispiele\Kapitel36>java HTTP_reverse Glueckskind
34
Die Umkehrung von 'Glueckskind' ist: 'dnikskceulG'
35
36.4 Das HTTP-Protokoll
36
Nachdem Sie jetzt gesehen haben, wie man von Java über einen URL eine HTTP-Verbindung aufbauen kann, ist es an der Zeit, das HTTP-Protokoll etwas genauer anzusehen.
37
Das aktuelle Protokoll trägt die Versionsnummer HTTP/1.1 und wird in dem Dokument RFC2616 der Network Working Group definiert. Wir wollen uns im Rahmen dieser Einführung allerdings nur auf einige wichtige Aspekte konzentrieren, die für sinnvolle und interessante Aufgaben benötigt werden. Wer größeren Wissensdurst verspürt, sollte sich das Originaldokument zulegen (ftp://ftp.isi.edu/in-notes/rfc2616.txt).
38 39
Das HTTP-Protokoll basiert auf einem Request/Response-Modell, bei dem ein Client, z. B. ein Webbrowser, über TCP/IP eine Anfrage an einen bestimmten Port (standardmäßig Port 80) schickt, auf die der Server in geeigneter Weise reagiert, z. B. indem eine HTML-Seite zurückgesendet wird. Der Nachrichtenaustausch zwischen Client und Server verläuft stateless, d.h., zwei nacheinander stattfindende Anfragen haben miteinander nichts zu tun und können sich nicht aufeinander beziehen, da der Server keine Daten über die letzte Anfrage speichert1. Das HTTP-Protokoll ist übrigens abwärtskompatibel, d.h., ein Client oder Server, der HTTP 1.0 oder gar 1
40 41 42
Weil dies manchmal aber wünschenswert ist, wurde die Cookie-Technik entwickelt: der Server schickt Informationen zum Client und lässt sie auf dessen Festplatte speichern.
1007
HTTP-Verbindungen
0.9 verwendet, kann trotzdem mit einem Partner kommunizieren, der die neueste Version 1.1 einsetzt. In solchen Fällen wird immer der kleinste gemeinsame Nenner – d. h. die kleinere Versionsnummer – gewählt, was natürlich bedeutet, dass Funktionalitäten und Fähigkeiten von neueren Versionen nicht genutzt werden können. Jede Nachricht von Client an Server und Server an Client besteht immer aus zwei Teilen, einem Header sowie dem eigentlichen Datenblock. Der Header enthält Steuerinformationen, z. B. das auszuführende Kommando (Methode genannt), die verwendete Protokollversion, Statusinfos, den Pfadanteil des URL sowie optionale Einträge. Der Datenblock selbst ist in der Regel ein HTML-Dokument oder Formulardaten. Es können aber auch ganz andere Daten sein, z. B. ein MP3-File. Wir schauen uns nun kurz die wesentlichen Inhalte einer HTTP-Anfrage bzw. -Antwort an.
36.4.1 Die HTTP-Anfrage (Request) Eine Anfrage hat den folgenden Grundaufbau: Methode URL HTTP/Version Allgemeiner Header Request Header Entity Header Request Entity
Die erste Zeile ist dabei die wichtigste, da hier das angeforderte Kommando (Methode) und der Pfadteil des URL angegeben werden, z.B. GET /index.html HTTP/1.1
Die wesentlichen Anfragemethoden, die ein Client an einen Server schicken kann, sind: ■
■
■
■
1008
GET ist die mit Abstand am häufigsten eingesetzte Methode und dient zur Anforderung eines Dokuments. GET bietet die Möglichkeit, zusätzliche Parameter (in x-www-form-urlencoded Format) an den URL anzuhängen. HEAD ist identisch zu GET mit dem Unterschied, dass der Server das gewünschte Dokument nicht zurücksendet, sondern nur Informationen über das Dokument, insbesondere Typ, Größe, etc. POST dient zur Übermittlung von Formulareingaben an den Server. Diese Daten werden im Anschluss an den Header im Datenbereich (RequestEntity) übertragen. PUT ermöglicht dem Client, auf dem Server bestehende Quellen zu modifizieren oder neue zu erzeugen. Dies setzt natürlich entsprechende Berechtigungen und Konfigurationen auf Serverseite voraus.
Das HTTP-Protokoll
■ ■
DELETE ist das Gegenstück zu PUT und löscht Dokumente auf dem Server, Berechtigung vorausgesetzt. OPTIONS fragt an, welche Aktionen der Server anbietet.
Die optionalen Header enthalten diverse Informationen, die für den Server hilfreich sein können. Alle Einträge in einem Header bestehen aus Schlüsselwörtern, gefolgt von einem Doppelpunkt und einer Zeichenkette. Ein typischer HTTP-Request könnte folgendermaßen aussehen:
29
GET /index.html HTTP/1.1 Date: Wed, 15 Nov 2001 06:25:24 GMT User-agent: Mozilla/4.6 Accept: text/html, text/plain
30 31
36.4.2 Die HTTP-Antwort (Response) Die Antwort des Servers ist in ihrem Aufbau ähnlich der HTTP-Anfrage und sieht folgendermaßen aus:
32
HTTP/Version Status ResponseMessage Allgemeiner Header Response Header Entity Header Resource Entity
33
Die erste Zeile enthält den Status und gibt Aufschluss darüber, ob die Anfrage des Clients erfolgreich war. Der Status ist im Kern eine schlichte Zahl, gefolgt von einer Kurzbeschreibung (Response Message), z.B.
35
34
36
HTTP/1.1 200 OK
Typische Werte für den Status sind: ■ ■ ■ ■
200 403 404 501
37
OK : die Anfrage wurde erfolgreich bearbeitet FORBIDDEN: für die Anfrage gibt es beim Server keine Erlaubnis NOT FOUND: die angeforderte Datei konnte der Server nicht finden NOT IMPLEMENTED: das angeforderte Kommando ist nicht vorhanden
38
Darüber hinaus gibt es noch zahlreiche weitere Statuscodes, die Sie im Buchanhang finden.
39
Nach der ersten Zeile mit dem Statuscode folgen in den restlichen Zeilen optionale Header mit Zusatzinformationen, z. B. die Länge der Daten, die gesendet werden, und um welchen Typ von Daten es sich handelt. Schließlich folgt nach einer Leerzeile – als Merkmal für das Ende des Headers – das angeforderte HTML-Dokument. Eine typische Antwort wäre z.B.
40 41
HTTP/1.1 200 OK Server: Apache/1.3 Content-type: text/html, text/plain Content-length: 453
42
1009
HTTP-Verbindungen
...die angeforderte HTML-Seite!
Für die Header – sowohl für Request wie auch Response – gibt es eine Fülle von Einträgen, die an dieser Stelle nur kurz angedeutet werden können. Mehr Informationen finden sich im schon erwähnten RFC 2616.
36.4.3 HTTP - Header Allgemeiner Header Wichtige Einträge im allgemeinen Header sind: ■
Connection spezifiziert Optionen für die aktuelle Verbindung, z. B. close weist den Server an, nach dem Senden der Antwort die Verbindung zu schließen (nur HTTP/1.1), während keep-alive anzeigt, dass der Server die Verbindung offen halten soll. Connection: close
■
Date gibt das aktuelle Datum inkl. Uhrzeit an, z. B.: Date: Wed, 15 Nov 2001 06:25:24 GMT
■
Transfer-Encoding gibt an, in welcher Codierung die Nachricht übertragen wurde (hat nichts mit Content-Encoding zu tun!), z. B.: Transfer-Encoding: chunked
Request-Header In diesem Header können zusätzliche Informationen über den Request oder den Client angegeben werden, z. B. ■ ■
User-Agent gibt Auskunft über die verwendete Client-Software (Browser-Typ). Accept definiert, welche Medientypen der sendende Client als Antwort verarbeiten kann. Diese Typen werden in der MIME-Notation Art/Format geschrieben: typische Werte für Text sind u. a. text/plain (einfacher Text), text/html (HTML), text/x-dvi (TeX-DVI), für Bilder image/gif, image/jpeg, z. B.: Accept: text/html, image/jpeg
■
Accept-Charset gibt an, welcher Zeichensatz für Textdaten vom Client verstanden wird, z. B.: Accept-Charset: iso-8859-1, unicode-1-1
Response-Header Hier können zusätzliche Informationen über die Antwort oder den Server angegeben werden, z. B.
1010
Das HTTP-Protokoll
■ ■
Server zeigt an, welche Software der Server verwendet. Retry-After gibt (im Fehlerfalle) an, ab wann der Server wieder ansprechbar ist (Anzahl Sekunden).
Entity-Header Hier werden Eigenschaften des Datenblocks angegeben. Häufig verwendete Attribute sind:
29
Content-Encoding definiert, welche Codierung verwendet worden ist bzw. verstanden wird, z. B. das Kompressionsverfahren:
30
■
Content-Encoding: gzip ■
Content-Length gibt an, wie groß in Bytes der Datenblock ist, z. B.:
31
Content-Length: 5436 ■
Content-Type zeigt an, von welchem Typ die Daten sind und welche Zeichencodierung verwendet wurde:
32
Content-Type: text/html; charset=iso-8859-1
33
Die Header-Informationen, vor allem die für den Client interessanten Einträge der Server-Antwort, sind bei der oben besprochenen Vorgehensweise über getOutputStream() der Klasse HttpURLConnection nicht abgreifbar. Hierfür dienen die folgenden Methoden von HttpURLConnection:
34 Tabelle 36.4: Wichtige Methoden der Klasse HttpURLConnection
35
Methode
Beschreibung
String getHeaderField(String)
Liefert zu einem Header-Schlüsselwort (z. B. »Server«) den zugehörigen Wert im Header.
String getResponseMessage()
Liefert die zum Statuswert zugehörige Beschreibung.
String method()
Die verwendete HTTP-Methode (das Kommando wie GET, PUT, usw.).
37
void setRequestProperty(String attr, String wert)
Setzt das Attribut attr auf den gewünschten wert. Dadurch wird der zu sendende RequestHeader um diesen Eintrag erweitert.
38 39
36.4.4 Ablauf einer HTTP-Kommunikation Betrachten wir nun den typischen Fall, dass ein Benutzer in seinem Webbrowser www.google.de eintippt, um sich die Seite anzuschauen. Dabei passiert Folgendes: 1.
2.
36
40 41
Der Browser bemerkt, dass der URL unvollständig ist und ergänzt ihn um die Protokollangabe http://. Dann stellt er eine socketbasierte TCP/IP-Verbindung zum Port 80 des Rechners mit dem Domain-Namen www.google.de her. Er sendet an den dort auf Port 80 lauschenden Webserver seinen HTTPRequest:
42
1011
HTTP-Verbindungen
GET / HTTP/1.1 Connection: keep-alive Date: Mon, 07 Jan 2002, 08:34:19 GMT Accept: */* Accept-Encoding: gzip, deflate User-Agent: Mozilla/4.0
3. Der Server lädt die angeforderte Seite von seiner Festplatte. Falls der URL keinen Dateinamen enthält, ergänzt der Server index.html oder, je nach Konfiguration, andere Standarddateinamen. Eventuell wird die Seite auch erst bei Anforderung dynamisch erzeugt (beispielsweise bei Einsatz von CGI oder JavaServer Pages). 4. Der Server erstellt eine HTTP-Response und schickt sie an den Client: HTTP/1.1 200 OK Date: Mon, 07 Jan 2002 08:34:20 GMT Content-Length: 4925 Content-Type: text/html Last-Modified: Mon, 07 Jan 2002 08:33:00 GMT Server: Jigsaw/2.2.0 Google usw.
5. Die Verbindung zwischen Client und Server wird beendet (Ausnahme: beide Seiten verwenden HTTP/1.1 und somit persistente Verbindungen, die erst auf Anforderung des Clients oder Timeout geschlossen werden, wie es hier im Beispiel der Fall ist). 6. Der Client verarbeitet die empfangene Datei und zeigt sie in seinem Fenster an. Beim Verarbeiten bemerkt er eventuell, dass die HTMLDatei Bilder enthält. Jedes Bild wird mit einer separaten GET-Anfrage vom Server angefordert, z. B.: GET /images/hp1.gif HTTP/1.1 Connection: keep-alive Date: Mon, 07 Jan 2002 08:34:20 GMT Accept: */* Accept-Encoding: gzip, deflate Referer: http://www.google.de/ User-Agent: Mozilla/4.0
7.
Der Server sucht die angeforderte Datei, erstellt eine geeignete Antwort und schickt sie dem Browser: HTTP/1.1 200 OK Date: Mon, 07 Jan 2002 8:34:21 GMT Content-Length: 2953 Content-Type: image/gif Server: Jigsaw/2.2.0 j8475fho9s8345hf9wrf438 // Bilddaten
Dieses Spielchen von Anfrage und Antwort zwischen Browser und Webserver geht so lange weiter, bis der Browser alle gewünschten Daten besitzt. Bei HTTP/1.1 wird dabei nicht jedes Mal für ein Anfrage/Antwort-Paar eine
1012
HTTP-Zugriffe über Proxy und Passwort
neue TCP/IP Verbindung aufgebaut (nur bei den älteren Versionen ist dies notwendig), sodass die Übertragung deutlich schneller ist. Außerdem erlaubt HTTP/1.1 Pipelining, d.h., der Browser kann mehrere Anfragen hintereinander schicken (z. B. wenn mehrere Bilder benötigt werden) und dann die einkommenden Antworten des Server verarbeiten.
29
Auf der Buch-CD finden Sie im Verzeichnis zu diesem Kapitel eine einfache Implementierung eines Webservers (Datei MiniWebserver.java). 1.
Legen Sie zuerst auf Ihrer Festplatte ein Verzeichnis für den Server an – beispielsweise c:\wwwserver\html. 2. Speichern Sie in diesem Verzeichnis einige HTML-Dokumente (ohne eingebettete Elemente wie Bilder, Frames etc.). 3. Starten Sie den Webserver. Übergeben Sie im Aufruf das Dokumentenverzeichnis des Webservers und die gewünschte Portnummer (80, wenn Sie keinen anderen Webserver laufen haben):
DISC
30 31 32
java MiniWebServer c:\wwwserver\html 80
33
4. Fordern Sie mit Ihrem Browser eine HTM-Datei vom Webserver an: http://localhost:80/kontakt.html
5. Beenden Sie den Server mit (Strg)+(C) oder über den Prozessmanager Ihres Betriebssystems.
34 35
36.5 HTTP-Zugriffe über Proxy und Passwort Was ist ein HTTP-Proxy?
36
Proxy ist wörtlich genommen ein Stellvertreter; dies beschreibt recht gut, was ein HTTP-Proxy macht. Er kommt meistens in Firmennetzwerken zum Einsatz und bildet die Schnittstelle zwischen einem Mitarbeiter-PC und dem Internet.
37
Wenn ein Mitarbeiter auf seinem PC mit einem Browser eine Webseite besuchen will, dann wird der Browser keine Verbindung zu dem gewünschten Webserver aufbauen, sondern er wird seine Anfrage zu dem Proxy-Server des Unternehmens umlenken. Der Proxy prüft nun je nach Konfiguration Verschiedenes, z.B.
38
Darf der Mitarbeiter Webseiten aus dem Internet betrachten? Eventuell wird nach einem Passwort gefragt. Ist die angeforderte Seite auf dem Server schon vorhanden? Falls ja, lade die Seite von der Festplatte und liefere sie an den Browser zurück. Falls nein, fordere die Seite aus dem Internet (von der angegebenen Webseite) an. Ist die Seite aufgrund unternehmensinterner Regelungen gesperrt, z. B. Erotik-Seiten, Spiele-Seiten, Jobbörsen, etc.? Falls ja, wird der Zugriff verweigert.
40
■ ■
■
39
41 42
1013
HTTP-Verbindungen
Ein Proxy-Server beschleunigt also zum einen den Internet-Zugriff, da von den Mitarbeitern häufig angeforderte Webseiten schon lokal vorliegen und nicht erst aus dem Internet besorgt werden müssen. Noch wichtiger ist jedoch die Kontrollfunktion, da durch den Proxy gesteuert werden kann, ob und was ein Mitarbeiter während seiner Arbeitszeit mit dem Browser anschaut. Proxy-Zugriff mit Java Wenn Sie Ihre Java-Programme mit Netzwerkfunktionalität innerhalb eines Unternehmensnetzwerkes ausführen und Ihr Unternehmen verwendet Proxies, dann werden Sie diverse Fehlermeldungen erhalten. Der Grund dafür liegt darin, dass die Java-Laufzeitumgebung standardmäßig von einer direkten Internetverbindung ausgeht und nichts von einem Proxy ahnt. Aber keine Sorge: dieser Ahnungslosigkeit kann man natürlich ein Ende setzen. Hierzu müssen Sie wissen, wie der Proxy-Server heißt (Domain-Name oder IP-Adresse) und auf welchem Port er lauscht, und dann im Java-Quelltext als erste Anweisung die folgenden Systemeigenschaften über die Properties-Klasse setzen: System.getProperties().setProperty( "proxySet", "true" ); System.getProperties().setProperty( "proxyHost", "der.prox.server"); System.getProperties().setProperty( "proxyPort", "85" );
In diesem Beispiel heißt der Proxy also der.prox.server und lauscht auf Port 85.
TIPP
Falls noch andere Proxies verwendet werden (z. B. für FTP-Zugriffe oder Socketverbindungen), müssen eventuell weitere Eigenschaften gesetzt werden (z. B. "ftpProxySet", "ftpProxyHost", "ftpProxyPort" oder "socksProxySet", "socksProxyHost", "socksProxyPort"). Fragen Sie Ihren Systemadministrator nach den Einstellungen. Passwortgeschützte Webseiten Bei etlichen Webservern sind ausgewählte oder aber alle Webseiten passwortgeschützt, d.h., man kann diese Seiten nur abrufen, wenn man gültige Credentials (Username und Passwort) vorweisen kann. Wenn ein Browser eine solche Seite anfordert, weist der Webserver den Request zurück und schickt eine Fehlermeldung, die dem Browser mitteilt, dass Benutzername und Passwort notwendig sind. Der Browser wird dann ein Fensterchen öffnen und den Benutzer um diese Daten bitten, und dann einen neuen Request losschicken, diesmal mit den Credentials.
1014
HTTP-Zugriffe über Proxy und Passwort
Abbildung 36.1: Passwortabfrage bei geschützter Webseite
29 30 Was aber soll man machen, wenn man von einem Java-Programm aus automatisch auf eine solche Seite zugreifen will?
31
Der geschilderte Passwort-Schutz beruht auf dem Authentifizierungsmechanismus des HTTP-Protokolls. Die Klasse HttpURLConnection bietet hierfür eine spezielle Methode an, mit der zusätzliche Eigenschaften (wie Authentifizierung) gesetzt werden können:
32
33
void setRequestProperty(String attr, String wert)
Das notwendige Attribut ist "Authorization" und muss auf einen String der Form "Basic benutzername:passwort" gesetzt werden. Der Stringteil benutzername:passwort muss dabei in der sogenannten BASE64-Codierung vorliegen. Diese Codierung liefert die (undokumentierte) Klasse sun.misc. BASE64Encoder aus dem JDK. Statt langer Worte schauen wir uns am besten gleich die notwendige Code-Sequenz an:
34 35
36
import sun.misc.BASE64Encoder; // eine BASE64 codierte Version von "benutzer:passwort" erzeugen String benutzer = "petermüller"; String passwort = "ballaballa"; BASE64Encoder enc = new BASE64Encoder(); String tmp = benutzer + ":" + passwort; String codiert = enc.encode(tmp.getBytes());
37
// das Authorization Attribut der HttpURLConnection setzen // vorher: HttpURLConnection verbindung = meinURL.openConnection(); verbindung.setRequestProperty("Authorization","Basic " + codiert);
39
38
40
Wie Sie sehen können, ist das Ganze ein Kinderspiel; man muss nur wissen, was zu tun ist. Die undokumentierte2 Klasse BASE64Encoder hat eine Methode encode(), die als Parameter ein Byte-Array erwartet. Aus diesem Grund muss aus dem Autorisierungsstring tmp durch Aufruf von getBytes() ein Byte-Array erzeugt werden.
41 42
2
Diese Klasse ist seit dem JDK1.1.7 vorhanden, wird aber in keiner Dokumentation beschrieben und Sun garantiert auch nicht, dass zukünftige JDK-Versionen diese Klasse weiterhin enthalten werden.
1015
HTTP-Verbindungen
Schließlich wird die bereits erwähnte setRequestProperty()-Methode für eine bestehende HttpURLConnection aufgerufen, um das Attribut "Authorization" zu setzen. Das war schon alles. Ab nun geht alles seinen gewohnten Gang (z. B. getInputStream() aufrufen etc.).
TIPP
1016
Wenn nicht das Dokument selbst geschützt ist, sondern die Passwort-Abfrage von einem dazwischengeschalteten Proxy-Server stammt, wird exakt der gleiche Mechanismus verwendet. Einziger Unterschied: mit setRequestProperty() wird nun das Attribut "Proxy-Authorization" anstelle von "Authorization" gesetzt.
Inhalt
37 Verteilte Anwendungen mit RMI 29 30
RMI steht für Remote Method Invocation und ist eine Technologie, die es ermöglicht, dass Java-Programme, die in verschiedenen Java Virtual Machines (JVM) laufen, miteinander kommunizieren können. Diese Kommunikation findet direkt auf Objektebene statt, d.h., eine Methode von einem Objekt in Programm A in JVM 1 kann eine Methode von einem Objekt in Programm B in JVM 2 aufrufen. Die laufenden VM können dabei sowohl auf dem gleichen Rechner oder aber auf verschiedenen Rechnern laufen, die über ein TCP/IP-Netzwerk miteinander verbunden sind.
31 32 33
Der Aufruf von Objektmethoden ist dabei bis auf Details völlig transparent und sieht genauso aus wie ein lokaler Methodenaufruf. Darüber hinaus ist es auch möglich, einem Programm Objekte zukommen und ausführen zu lassen, die ihm völlig unbekannt sind und deren Definitionen ihm erst zur Laufzeit bekannt gemacht werden.
34 35
RMI bedient sich im Wesentlichen eines eigenen Kommunikationsprotokolls namens JMPR, das über TCP/IP-basierte Sockets für den Datenaustausch zwischen den Objekten verantwortlich ist. Als Programmierer könnte man also durchaus einen eigenen RMI-Mechanismus erstellen, was aber extrem aufwändig und fehleranfällig wäre, da viele Details zu beachten sind. Dies alles wird uns von RMI abgenommen. RMI ist übrigens das Java-Gegenstück zu CORBA (Common Object Request Broker Architecture). Der wesentliche Unterschied zwischen RMI und CORBA liegt darin, dass RMI vornehmlich für Java-Java-Interaktionen konzipiert wurde, während CORBA sprachunabhängig ist.
36
37 CORBA
38 39
Grundarchitektur 40
Das wesentliche Ziel von RMI ist die Transparenz von Methodenaufrufen, d.h., ein Objekt in einer anderen JVM (ein sogenanntes Remote-Objekt) soll sich so anfühlen und benehmen wie ein lokales Objekt. Neben der Unterscheidung von remote und lokalem Objekt gibt es noch die zugehörige Unterteilung in Server und Client, je nachdem, zu wem ein Objekt gehört: aus der Sicht eines Programms sind alle Objekte, die in einer anderen JVM residieren, Remote-Objekte, die von einem Server(programm) bereitgestellt werden. Umgekehrt sind für dieses Serverprogramm alle anfragenden Pro-
41 42
1017
Index
37.1
Verteilte Anwendungen mit RMI
gramme Clients. Dabei kann natürlich auch ein Server wiederum Methoden von anderen – aus seiner Sicht – remote vorhandenen Objekten aufrufen und somit ein Client sein. Der Datenaustausch zwischen Client und Server ist nach einem Schichtenmodell folgendermaßen aufgebaut: Abbildung 37.1: Das Schichtenmodell von RMI
Client
Server
Stub
Skeleton
Remote Reference
Remote Reference
TCP/IP
TCP/IP
Auf der obersten Schicht befinden sich Client und Server. Wenn der Client eine Methode eines Remote-Objekts aufrufen will, hat er zunächst das Problem, dass dieses Objekt auf seiner Seite gar nicht existiert (sondern nur beim Server). RMI löst diesen Aspekt, indem der Client den Aufruf auf einem Stellvertreter-Objekt (Stub) durchführt, das für den Client identisch zum gewünschten Remote-Objekt aussieht. Der Stub kümmert sich beim Aufruf darum, die Parameter der aufgerufenen Methode in einen Byte-Stream zu konvertieren. Dann sorgt er dafür, dass eine TCP/IP-Verbindung zu dem gewünschten Server (bzw. der remote JVM) aufgebaut wird und die Daten übertragen werden. Dieser Datenaustausch erfolgt logisch auf der darunter liegenden Remote Reference Schicht gemäß eines speziellen RMI-Protokolls (meist JRMP = Java Remote Method Protocol), das die TCP/IP-Verbindung regelt. Auf der Server-Seite werden nun in umgekehrter Richtung die Schichten durchlaufen. Die ankommenden Daten werden von der TCP/IP und Remote Reference Schicht empfangen und an einen Stub weitergereicht, der auf Server-Seite allerdings üblicherweise Skeleton genannt wird. Der Skeleton konvertiert den empfangenen Byte-Stream wieder in passende Parameter und ruft schließlich die vom Client gewünschte Server-Methode auf. Dann wartet er auf den Rückgabewert, wandelt ihn wieder in einen Byte-Stream um und schickt das Ergebnis via TCP/IP und JRMP an den Stub auf Client-Seite zurück. Der Stub führt noch mal eine Konvertierung der empfangenen Daten durch und gibt dann den Rückgabewert an den aufrufenden Client zurück.
1018
Objekte finden: die RMI-Systemregistrierung
Aus der Sicht des Clients war der Aufruf der Methode ein ganz normaler – lokaler – Methodenaufruf. Die umfangreiche Arbeit, die hinter den Kulissen notwendig war (Aufbau einer Verbindung zur anderen JVM, Umwandlung und Versand der Parameter etc.), hat auf Client-Seite der Stub gemacht, auf Serverseite das Skeleton als sein Gegenstück. Ab JDK 1.2 wird übrigens kein expliziter Skeleton auf Serverseite mehr benötigt, da dessen Aufgaben durch die Verwendung der neuen Java-Reflection-Technologie abgedeckt werden. Aus Programmiersicht ändert sich jedoch nichts.
29
Die vielen Schritte im oben geschilderten Ablauf machen aber auch deutlich, dass ein entfernter Methodenaufruf ziemlich kostspielig ist und um mehrere Größenordnungen länger dauern kann als ein lokaler Aufruf.
30 31
37.2 Objekte finden: die RMI-Systemregistrierung
32
Der im vorherigen Abschnitt geschilderte Ablauf eines Remote-Aufrufs hat einige wichtige Details unterschlagen: wie weiß man auf Clientseite, wo die andere JVM mit dem Serverobjekt läuft, d.h., zu welchem Rechner soll die TCP/IP-Verbindung aufgebaut werden? Und vor allem: woher kommt der Stub für den Client?
33 34
Die Lösung heißt RMI-Systemregistrierung. Es handelt sich dabei um ein Java-Programm (rmiregistry), das auf dem gleichen Rechner läuft wie das Programm mit den Remote-Objekten und eine Liste verwaltet, in der Objektnamen auf URLs abgebildet werden. Die Verwendung der Systemregistrierung lässt sich in verschiedene Phasen einteilen. Das typische Vorgehen sieht folgendermaßen aus:
35
36 37
1.
Die Systemregistrierung wird auf dem Hostrechner gestartet (der gleiche, auf dem auch die Remote-Objekte laufen). 2. Das Serverprogramm wird gestartet, erzeugt seine Remote-Objekte und meldet sie bei der Systemregistrierung an, inklusive der Codebase (= URL mit Ort, wo der Klassencode zu finden ist). 3. Ein Client-Programm wird gestartet und fragt bei der Systemregistrierung nach einer Referenz auf das gewünschte Remote-Objekt. 4. Die Systemregistrierung liefert die gewünschte Remote-Referenz, inkl. eines URL, wo der Klassencode des Stubs zu finden ist (Codebase). 5. Falls die Klassendefinition des Stubs lokal im CLASSPATH auf dem ClientRechner vorliegt, lädt der Client die Klassendefinition von seiner Festplatte, andernfalls wird eine Netzwerkverbindung zu dem URL aufgebaut, wo die Stubdefinition zu finden ist, und der Code wird angefordert. Dies muss natürlich für den Client möglich sein, z. B. durch einen FTP- oder Webserver, der auf dem Serverhost läuft und solche Anfragen bedient.
38 39 40 41 42
1019
Verteilte Anwendungen mit RMI
6. Der FTP/Webserver liefert den Stubcode zurück. 7. Der Client ruft die gewünschte Methode des Stubs (und somit des Remote-Objekts) auf. Abbildung 37.2: Das Zusammenspiel von Client, Server und Systemregistrierung
2. Remote-Objekt suchen
Registry
Client 3. Remote-Referenz
de ho et M 6. f ru uf na
Server 1. Objekte anmelden
5. Stub senden
Codebase 4. Stub-Definition anfordern
37.3 Erstellen einer lokalen RMI-Anwendung Nachdem der theoretische Unterbau nun klar ist, wird es höchste Zeit, eine konkrete RMI-Anwendung zu erstellen. Hierbei sind die folgenden Punkte abzuarbeiten: 1.
2.
3.
4. 5. 6. 7. 8. Beispiel
1020
Zunächst muss eine Schnittstelle definiert werden, welche das RemoteObjekt und seine für Clients aufrufbaren Remote-Methoden definiert (= Remote-Schnittstelle). Eine Implementierung der Remote-Schnittstelle muss erstellt werden, d.h., hier wird die Klassendefinition des gewünschten Remote-Objekts realisiert. Ein Server wird erstellt, der die Remote-Objekte erzeugt und bei der RMI-Systemregistrierung anmeldet. Durch die Anmeldung können andere Java-Programme (die Clients) das Remote-Objekt finden und aufrufen. Der Client wird definiert, der das Remote-Objekt einsetzt. Die notwendigen Stub- und Skeletonklassen werden erzeugt. Die RMI-Systemregistrierung wird gestartet. Der Server wird gestartet. Der Client wird gestartet.
Sie werden nun im Folgenden diese Schritte anhand eines Beispiels im Detail nachvollziehen. Als RMI-Anwendung wurde ein Uhrzeit-Dienst gewählt, der auf einem Server läuft und anfragenden Clients die aktuelle Uhrzeit mitteilt. So etwas ist bei vielen verteilten Anwendungen sehr nützlich, da zwar jeder PC eine eigene Uhr hat, diese aber nicht immer sonder-
Erstellen einer lokalen RMI-Anwendung
lich genau sind, sodass fünf PCs meistens auch fünf verschiedene Uhrzeiten anzeigen. Bei einer verteilten Anwendung ist dies oft nicht akzeptabel. Ziel ist es daher, ein Remote-Objekt ZentralUhr anzulegen, auf das jeder Client zugreifen kann, wenn er die aktuelle Uhrzeit benötigt. Dieser Abschnitt ist zunächst einfach gehalten und geht bei den folgenden Schritten davon aus, dass alle .java- und .class-Dateien auf demselben Rechner im selben Verzeichnis liegen und die Hauptakteure RMI-Systemregistrierung, Serverprogramm und Clientprogramm ebenfalls auf der gleichen Maschine laufen. Dessen ungeachtet sollte auf Ihrem PC ein funktionierender TCP/IP-Stack installiert sein! In einem späteren Abschnitt wird das Programm zu einer echt verteilten Anwendung ausgebaut, die auf verschiedenen Rechnern läuft.
29 30 31
1. Definition der Remote-Schnittstelle 32
Die Schnittstelle dient zur Deklaration des Remote-Objekts und seiner zu exportierenden Methoden, stellt aber keine Implementierung zur Verfügung. Sie muss public deklariert sein und eine spezielle RMI-Schnittstelle erweitern: java.rmi.Remote.
33
Außerdem gilt es zu beachten, dass jede Methode eine Exception vom Typ java.rmi.RemoteException werfen muss. Ferner gilt: Falls eine Methode andere Remote-Objekte als Parameter oder Rückgabewert verwenden will, dann muss es sich um Schnittstellen-Typen handeln und nicht um die Implementierungsklassen.
34 35
Die Schnittstelle für unsere Klasse ZentralUhr sieht so aus: Listing 37.1: ZentralUhrSchnittstelle.java
36
import java.rmi.*; import java.util.*;
37
public interface ZentralUhrSchnittstelle extends Remote {
38 public Date aktuelleZeit() throws RemoteException; }
39
2. Implementierung der Remote-Schnittstelle 40
Nun müssen Sie eine Klasse erstellen, die Ihre Schnittstelle implementiert. Eine solche Klasse unterscheidet sich fast überhaupt nicht von einer normalen Java-Klasse. Der einzige Unterschied: Sie muss von der Klasse java.rmi.server.UnicastRemoteObject abgeleitet werden1. Außerdem muss sie einen Standardkonstruktor (also ohne Parameter) besitzen, der eine RemoteException wirft.
1
41 42
Was zu tun ist, falls dies nicht möglich ist (weil schon eine andere Basisklasse verwendet wird), sehen Sie in Kapitel 37, Abschnitt »Remote-Implementierung ohne UnicastRemoteObject«.
1021
Verteilte Anwendungen mit RMI
Listing 37.2: ZentralUhr.java import java.rmi.*; import java.rmi.server.*; import java.util.*; public class ZentralUhr extends UnicastRemoteObject implements ZentralUhrSchnittstelle { public ZentralUhr() throws RemoteException { } // die Remote-Methode public Date aktuelleZeit() throws RemoteException { return new Date(); } }
TIPP
Die Implementierungsklasse kann noch beliebig viele andere Methoden definieren (die nicht in der Remote-Schnittstelle vorkommen). Diese sind dann aber nur lokal verfügbar und nicht über RMI von einem externen Client aus. 3. Implementierung des Server-Programms Das Serverprogramm macht nicht viel: Es legt Instanzen der RemoteObjekte (hier also von ZentralUhr) an und meldet sie bei der Systemregistrierung an. Die Registrierung erfolgt über die Klasse java.rmi.Naming und deren statische Methoden bind() und rebind():
Tabelle 37.1: Methoden der Klasse Naming zur Registrierung
void bind(String url, Object obj);
Registriert das Objekt obj. Das Argument url enthält die Adresse der Systemregistrierung sowie einen Namen für das Objekt, unter dem es der »Welt« bekannt gemacht wird. Falls url schon verwendet wurde, wird eine AlreadyBoundException geworfen.
void rebind(String url, Object obj);
Analog zu bind(). Ersetzt eventuell schon vorhandene Bindungen.
Der URL hat dabei folgenden Aufbau: rmi://host:port/name host steht für den Rechnernamen, auf dem die Systemregistrierung läuft, und port zeigt die Portnummer an, unter der die Systemregistrierung auf Anfragen lauscht. Wird kein Host angegeben, wird localhost angenommen und wenn kein Port angegeben wird, kommt der Defaultwert 1099 zur Anwendung. name ist ein beliebiger String, unter dem das Objekt von Clients angefordert werden kann.
In der folgenden Implementierung gehen wir, wie schon erwähnt, davon aus, dass sich alles auf einem einzigen Rechner abspielt, d.h., die Systemregistrierung läuft somit auf dem gleichen Rechner wie das Serverprogramm. 1022
Erstellen einer lokalen RMI-Anwendung
Listing 37.3: ZentralUhrServer.java import java.rmi.*; public class ZentralUhrServer { public static void main(String[] args) { System.out.println();
29
try { // Objekt anlegen und anmelden ZentralUhr uhr = new ZentralUhr();
30
// Kurzform für rmi://localhost/ZentralUhrService" Naming.rebind("/ZentralUhrService", uhr);
31
} catch(Exception e) { System.err.println(" ZentralUhrServer: Exception " + e); }
32
} }
33
Die try-catch Klammerung ist notwendig, da rebind() diverse Exceptions auslösen kann, z. B. MalformedURLException.
34
4. Implementierung des Client-Programms Das Client-Programm sieht nicht viel anders aus als ein ganz normales JavaProgramm (dies ist ja auch schließlich Sinn und Zweck von RMI: RemoteZugriffe sollen möglichst unsichtbar sein!). Der Client muss sich als einzige Vorbereitung eine Referenz auf das Remote-Objekt bei der richtigen Systemregistrierung besorgen. Hierfür dient wieder die Klasse java.rmi.Naming mit der Methode lookup(): Object lookup(String url);
Anfrage nach einer Remote-Referenz. Ort der Systemregistrierung und Name der gewünschten Klassen stehen in url (analog zu bind()).
35
36 Tabelle 37.2: Methode der Klasse Naming
Wichtig beim Einsatz von lookup(): Der Rückgabewert ist eine Referenz auf den Schnittstellen-Typ und nicht auf die Implementierungsklasse, d.h., der Typ ist in unserem Beispiel ZentralUhrSchnittstelle (und nicht ZentralUhr). Ein Client hantiert immer nur mit den Schnittstellen-Typen von Remote-Objekten!
37 38 39 40
Listing 37.4: ZentralUhrClient.java
41
import java.rmi.*; import java.util.*;
42
public class ZentralUhrClient { public static void main(String[] args) { System.out.println();
1023
Verteilte Anwendungen mit RMI
try { // Anfrage an Registry stellen: // Kurform für "rmi://localhost/ZentralUhrService" ZentralUhrSchnittstelle uhr = (ZentralUhrSchnittstelle) Naming.lookup("/ZentralUhrService"); // der Remote-Aufruf Date aktZeit = uhr.aktuelleZeit(); DateFormat tf = DateFormat.getTimeInstance(DateFormat.LONG); System.out.println(" Client: Uhrzeit ist " + tf.format(aktZeit)); } catch(Exception e) { System.err.println(" Client: Exception " + e); } } }
5. Generierung von Stub/Skeleton-Klassen Dieser Schritt ist sehr einfach, da hierfür im JDK das Programm rmic mitgeliefert wird. Es erwartet als Aufrufparameter den Namen der Remote-Implementierungsklasse: rmic ZentralUhr
Voraussetzung für den Einsatz von rmic ist allerdings, dass die bisher in Schritt 1 bis 4 erstellten Java-Dateien kompiliert worden sind. Falls Sie dies noch nicht getan haben, holen Sie dies jetzt nach2!
TIPP
Achtung: Der Einsatz von rmic ist nicht notwendig, wenn sowohl auf Clientals auch auf Serverseite eine Java-Version ab 1.5 (Java 5) zum Einsatz kommt (in diesem Fall wird die notwendige Stub/Skeleton-Information dynamisch zur Laufzeit mithilfe von Reflection generiert)! Wenn jedoch eine der beiden Seiten mit einer älteren Java-Version kompiliert worden ist, dann muss rmic eingesetzt werden. Die Ausgabe von rmic sind zwei Klassen: ZentralUhr_Stub.class und ZentralUhr_Skel.class, die bereits erwähnten Stellvertreterklassen auf Client- und Serverseite. Ab JDK1.2 wird allerdings ZentralUhr_Skel.class nicht mehr benötigt. Falls Sie die Skeleton-Klasse nicht haben wollen, können Sie die Erzeugung durch die Option -v1.2 unterdrücken: rmic -v1.2 ZentralUhr
Damit sind die Implementierungsaspekte erledigt und es kann richtig losgehen.
2
1024
Wenn die Dateien, wie in diesem Beispiel, in einem Verzeichnis liegen, können Sie mit javac *.java alle Dateien in einem Zug kompilieren.
Erstellen einer lokalen RMI-Anwendung
6. Starten der RMI-Systemregistrierung Das Starten der Systemregistrierung erfolgt vorzugsweise über das Programm rmiregistry. Öffnen Sie also eine neue Konsole, wechseln Sie in das Verzeichnis mit den oben erzeugten Class-Dateien und rufen Sie rmiregistry auf. 7. Starten des Server-Programms
29
Öffnen Sie nun eine weitere Konsole für das Serverprogramm, wechseln Sie in das richtige Verzeichnis und starten Sie das Serverprogramm.
30
java ZentralUhrServer
8. Starten des Client-Programms
31
Nun wird es spannend. Öffnen Sie noch eine weitere Konsole und rufen Sie den Client auf:
32
java ZentralUhrClient
Abbildung 37.3: Ausgabe des RMI-Clients
33 34 35
36 Ausgesuchte RMI-Probleme
37
Wenn Sie sich exakt an die obigen Schritte gehalten haben, sollte der Aufruf des Clients das gewünschte Ergebnis gebracht haben. Falls nicht, dann wird es leider schwierig, an dieser Stelle konkrete Hinweise zu geben. Überprüfen Sie folgende Punkte:
38
■
■
39
Läuft auf Ihrem Rechner ein TCP/IP-Stack? Können Sie also z. B. eine Verbindung ins Internet herstellen oder einen anderen Rechner in Ihrem lokalen Netzwerk anpingen3? Bei Meldungen der Art ClassNotFound: Ist auf Ihrem System die Umgebungsvariable CLASSPATH gesetzt (siehe Kapitel 3.2, Abschnitt »Anpassen des Systems«)? Falls ja: stellen Sie sicher, dass sie das Verzeichnis enthält, in dem die oben erzeugten Class-Dateien liegen. Starten Sie dann neue Konsolenfenster und beginnen Sie mit Schritt 6.
40 41 42
3
Auf Konsole ping eingeben und abschicken.
1025
Verteilte Anwendungen mit RMI
■
Falls beim Client-Aufruf eine Meldung der Art unexpected hostname erscheint, stimmt eventuell die Hostadresse des Servers nicht. Starten Sie das Serverprogramm folgendermaßen: java -Djava.rmi.server.hostname=localhost ZentralUhrServer
Über den Schalter -D werden Systemeigenschaften (Properties) der JVM gesetzt. In diesem Fall ist es java.rmi.hostname. Andere Werte: statt localhost eine IP-Adresse. Manche Netzwerkrechner haben mehrere IP-Adressen, die Sie über ipconfig.exe (Windows) bzw. ifconfig (Linux, man beachte das f!) herausfinden können.
37.4 Parameterübergabe Nachdem Sie nun erfolgreich eine RMI-Anwendung erstellt haben, wollen wir uns einigen Details widmen. Ein wichtiger Aspekt ist die Frage, wie Methodenparameter und Rückgabewerte behandelt werden. Dazu sollten Sie sich kurz das normale Vorgehen bei lokalen Methodenaufrufen in Erinnerung rufen, also bei Aufrufen innerhalb einer JVM. Hier gibt es zwei Fälle: ■ ■
Primitive Datentypen, die als Kopie übergeben werden Objekte, für die nur eine Referenz übergeben wird
Bei RMI-Anwendungen gibt es hingegen beim Aufruf einer RemoteMethode drei wesentliche Fälle: ■
■
■
■
1026
Primitive Datentypen Sie werden als Kopie übergeben. Lokale Objekte Hier kann keine Referenz übergeben werden, da sie nur innerhalb der aufrufenden JVM einen Sinn hat. Objekte werden daher in einen ByteStream umgewandelt (serialisiert) und an die andere JVM bzw. das Remote-Objekt gesendet. Voraussetzung hierbei ist, dass das Objekt die Schnittstelle Serializable implementiert. Bei den meisten Klassen aus den Standard-Paketen von Java ist dies der Fall, sodass man sich darum nicht weiter kümmern muss. Wenn ein zu serialisierendes Objekt selbst Referenzen auf weitere Objekte enthält, dann werden auch diese serialisiert (vorausgesetzt, das geht), d.h., es wird unter Umständen ein ganzer Baum von Objekten in einen Byte-Stream umgewandelt und über das Netzwerk geschickt. Remote-Objekte Ein Client könnte auch ein Remote-Objekt (z. B. von einem lookup()Aufruf) als Parameter übergeben. In diesem Fall wird nicht das Remote-Objekt an sich serialisiert, sondern nur der Stub. Er wird über das Netzwerk gesendet.
Parameterübergabe
Falls eine RMI-Anwendung über mehrere Rechner verteilt ist, kann z. B. ein Programm B eine Remote-Referenz X von einem Serverprogramm A anfordern und in einem Remote-Aufruf zur Weiterverarbeitung an Programm C übergeben. Wenn C nun eine Methode von X – also einem Remote-Objekt, das von A bereitgestellt wird – aufruft, wird eine Kommunikation zwischen C und A stattfinden. Falls man das Spiel weitertreibt und auch A eine Remote-Referenz auf X (also sein eigenes Objekt) zukommen lässt, dann würde A einen entsprechenden Methodenaufruf auch als RMI-Aufruf über den TCP/IP-Stack durchführen. Solche Zusammenhänge gilt es beim Erstellen von größeren RMI-Anwendungen im Auge zu behalten.
x x.a r
Stub_y
loo
A
)
p(x ku
y.bearbeite(x)
B
x b_ Stu
lookup(y)
■
29 30 Abbildung 37.4: Zusammenspiel von A, B, C bei Remote-Referenzen
C
32
33
be
it()
31
34
y
35
Da beim Versenden von Objekten letztlich Bytecode zwischen verschiedenen JVM transferiert wird, ist immer auch der RMI-Classloader beteiligt, der den Code vom lokalen CLASSPATH oder einer definierten Codebase lädt.
36
Um die Parameterübergabe etwas zu üben, werden Sie nun das bisherige Beispiel ZentralUhr um eine Methode erweitern, welche die Uhrzeit ermittelt und in einem als Parameter übergebenen Objekt abspeichert und an den Aufrufer (den Client also) zurückgibt. Die Servermethode erwartet hierzu lediglich, dass das übergebene Objekt eine vorgegebene Schnittstelle (hier mit Namen UhrArgumentSchnittstelle) implementiert und dadurch eine Methode setzeUhrzeit() zum Speichern eines Date-Objekts besitzt.
37 38 39
Zur Veranschaulichung werden die notwendigen Änderungen Schritt für Schritt beschrieben.
40 1. Definition der Remote-Schnittstelle und UhrArgumentSchnittstelle Sie legen zunächst eine neue Schnittstelle namens UhrArgumentSchnittstelle an, die alle Klassen, die ein Client als Argument an unsere RemoteMethode übergeben will, implementieren muss:
41
Listing 37.5: UhrArgumentSchnittstelle.java
42
import java.util.Date; import java.io.Serializable;
1027
Verteilte Anwendungen mit RMI
public interface UhrArgumentSchnittstelle extends Serializable { void setzeUhrzeit(Date dat); }
Die Schnittstelle deklariert die Methode setzeUhrzeit(), sodass später unsere Remote-Methode die Uhrzeit im übergebenen Objekt selbst abspeichern kann. Da die Objekte als Parameter bzw. Rückgabewert bei einem RMI-Aufruf verwendet werden sollen, muss zusätzlich dafür gesorgt werden, dass sie serialisiert werden können. Daher wird der Schnittstellen-Typ von der Marker-Schnittstelle Serializable abgeleitet. Die Remote-Schnittstelle ändert sich kaum. Es wird lediglich die neue Methode aktuelleZeit() hinzugefügt, die als Parameter- und Rückgabetyp UhrArgumentSchnittstelle erwartet: Listing 37.6: ZentralUhrSchnittstelle.java import java.rmi.*; import java.util.*; public interface ZentralUhrSchnittstelle extends Remote { public Date aktuelleZeit() throws RemoteException; public UhrArgumentSchnittstelle aktuelleZeit( UhrArgumentSchnittstelle arg) throws RemoteException; }
2. Implementierung der Remote-Schnittstelle Anschließend wird die zusätzliche Methode in der Implementierungsklasse ZentralUhr hinzugefügt: Listing 37.7: ZentralUhr.java import java.rmi.*; import java.rmi.server.*; import java.util.*; public class ZentralUhr extends UnicastRemoteObject implements ZentralUhrSchnittstelle { public ZentralUhr() throws RemoteException { } // die Remote-Methode public Date aktuelleZeit() throws RemoteException { return new Date(); } // die neue Remote-Methode public UhrArgumentSchnittstelle aktuelleZeit( UhrArgumentSchnittstelle arg) throws RemoteException { Date tmp = new Date();
1028
Parameterübergabe
// im übergebenen Objekt speichern arg.setzeUhrzeit(tmp); // Objekt zurückgeben return arg; } }
29
Die Methode erwartet das Objekt vom Typ UhrArgumentSchnittstelle, übergibt ihm ein neues Date-Objekt und liefert den Parameter als Rückgabewert. Beachten Sie: Bei einer nur lokal aufzurufenden Methode hätte es gereicht, setzeUhrzeit() auszuführen. Der Aufrufer von aktuelleZeit() hätte dann die Änderung in seinem Objekt vorliegen, das er als Parameter übergeben hatte. Bei einem RMI-Aufruf arbeitet unsere Remote-Methode jedoch auf einer richtigen Kopie, d.h., das vom Aufrufer übergebene Objekt bleibt unverändert. Die geänderte Kopie muss daher explizit wieder als Rückgabewert zurückgesendet werden.
30 31 32
3. Implementierung des Server-Programms
33
Hier ändert sich gar nichts. 4. Implementierung des Client-Programms
34
Beim Client wird noch eine Klasse Dummy definiert, welche Gebrauch von der Schnittstelle UhrArgumentSchnittstelle macht. Außerdem wird ein zusätzlicher Aufruf der neuen Remote-Methode aktuelleZeit(UhrArgumentSchnittstelle arg) hinzugefügt.
35
Listing 37.8: ZentralUhrClient.java
36
import java.rmi.*; import java.util.Date; import java.text.DateFormat;
37
public class ZentralUhrClient { public static void main(String[] args) { System.out.println();
38 39
try { // Anfrage an Registry stellen: // Kurzform für "rmi://localhost/ZentralUhrService" ZentralUhrSchnittstelle uhr = (ZentralUhrSchnittstelle) Naming.lookup("/ZentralUhrService");
40 41
// der Remote-Aufruf ohne Parameter Date aktZeit = uhr.aktuelleZeit(); DateFormat tf = DateFormat.getTimeInstance(DateFormat.LONG); System.out.println(" Client: Uhrzeit ist " + tf.format(aktZeit));
42
Dummy tmp = new Dummy();
1029
Verteilte Anwendungen mit RMI
tmp.setzeUhrzeit(new Date()); Thread.sleep(2000);
// 2 Sekunden warten
// der Remote-Aufruf mit Parameter (lokales Objekt) Dummy erg = (Dummy) uhr.aktuelleZeit(tmp); System.out.println(" + System.out.println(" +
Zeit in tmp ist: " tf.format(tmp.meineZeit)); Zeit in erg ist: " tf.format(erg.meineZeit));
} catch(Exception e) { System.err.println(" Client: Exception " + e); } } } class Dummy implements UhrArgumentSchnittstelle { public Date meineZeit; public void setzeUhrzeit(Date arg) { meineZeit = arg; } }
5. Generierung von Stub/Skeleton-Klassen Nun werden, wie gehabt, alle .java-Dateien kompiliert und dann rmic aufgerufen: rmic ZentralUhr
TIPP
Achtung: Der Einsatz von rmic ist nicht notwendig, wenn sowohl auf Clientals auch auf Serverseite eine Java-Version ab 1.5 (Java 5) zum Einsatz kommt (in diesem Fall wird die notwendige Stub/Skeleton-Information dynamisch zur Laufzeit mithilfe von Reflection generiert)! Wenn jedoch eine der beiden Seiten mit einer älteren Java-Version kompiliert worden ist, dann muss rmic eingesetzt werden. 6./7./8. Starten der Programme Jetzt kann es losgehen. Öffnen Sie drei Konsolen im richtigen Verzeichnis mit allen Class-Dateien und starten Sie: rmiregistry java ZentralUhrServer java ZentralUhrClient
Vergleichen Sie die Ausgabe des Clients. Der Wert von tmp.meineZeit hat sich nicht geändert, da die Remote-Methode auf einer echten Kopie gearbeitet hat (wenn allerdings der Server seine Methode selbst lokal aufrufen würde, dann handelt es sich um einen normalen Methodenaufruf und das übergebene Objekt wird per Referenz angesprochen und somit verändert!).
1030
Erstellen einer verteilten RMI-Anwendung
37.5 Erstellen einer verteilten RMI-Anwendung Nachdem Sie nun alle wesentlichen Grundlagen von RMI kennen, ist es an der Zeit, den Sprung zum eigentlichen Anwendungsbereich von RMI zu machen, nämlich dem Erstellen von Java-Anwendungen, deren Client/Server-Komponenten auf verschiedenen Rechnern laufen.
29
Dabei ändert sich am Erstellen des RMI-Codes im Vergleich zum bisher besprochenen lokalen Fall fast nichts und die typische Abfolge an Schritten in Abschnitt 37.3 bleibt gleich!
30
Der Hauptunterschied liegt im Laden des Codes: Bei der Ausführung einer RMI-Anwendung wird Bytecode geladen. Die wesentlichen Fälle sind dabei:
31
Laden von Stubs Wenn der Client ein Remote-Objekt verwenden will, muss er zuvor den Bytecode der Stub-Klasse laden. Dazu prüft eine besondere Komponente der JVM – der Classloader –, ob sich die gewünschte Stubdefinition im lokalen Verzeichnis bzw. dem CLASSPATH befindet. Wenn dieser Fall gegeben ist, wird die Klasse geladen (bzw. ab Java 1.5 wird diese Information dynamisch mithilfe von Reflection generiert).
32
33 34
Wenn der Stub aber lokal nicht zu finden ist, dann versucht der Classloader, den Stub von dem entsprechenden Server zu laden (den Ort hat der Client über die RMI-Systemregistrierung beim lookup() erfahren). Dies setzt voraus, dass der Server diesen Ladevorgang ermöglicht. Typische Mechanismen sind ein Webserver (Zugriff über HTTP), ein FTP-Server, oder ein Fileserver (z. B. NFS). Am flexibelsten und häufigsten ist die HTTP-Variante, die im Folgenden auch verwenden wird.
35
36
Wichtig ist jedoch ein feiner Unterschied zwischen dem Laden vom CLASSPATH und dem Laden von einem anderen Rechner: das Java-Sicherheitsmodell geht davon aus, dass Bytecode aus dem CLASSPATH als vertrauenswürdig gilt, während alles Fremde von einem anderen Rechner zunächst mal als zweifelhaft gilt. Beim Laden von Stubs von einem anderen Rechner muss daher der Client dieses Laden zulassen, indem er einen speziellen SecurityManager einsetzt.
37 38 39
Laden von Parameter-Klassen
40
Wenn ein Client eine (für ihn) lokale Klasse als Parameter an einen RemoteAufruf übergibt, prüft der Server, ob er die Klassendefinition lokal bei sich im CLASSPATH finden kann, und lädt gegebenenfalls den Bytecode. Falls nicht, lädt er die Klassendefinition von der Codebase des aufrufenden Clients über das Netzwerk. In diesem Fall liegt wieder eine zunächst nicht vertrauenswürdige Quelle vor und, damit der Classloader dennoch laden darf, muss auf Serverseite ebenfalls ein Security-Manager eingesetzt werden.
41 42
1031
Verteilte Anwendungen mit RMI
TIPP
In vielen Lehrbüchern wird fälschlicherweise behauptet, dass auf Client- und Serverseite jeweils ein Security-Manager eingesetzt werden muss, damit RMI überhaupt funktioniert. Dies stimmt so nicht! Nur wenn Code aus einer unsicheren Quelle geladen wird (und nicht aus dem lokalen CLASSPATH), ist ein Security-Manager notwendig. Man kann bei einer RMI-Anwendung somit zwischen verschiedenen Szenarien unterscheiden: 1.
Alle verwendeten Klassen liegen als Class-Dateien sowohl auf Clientrechner als auch auf Serverrechner im jeweiligen CLASSPATH vor. Dieser Fall entspricht vom Wesen her Abschnitt 37.3. Es sind keine weiteren Änderungen für eine lauffähige RMI-Anwendung notwendig. 2. Die vom Client benötigten Klassen (Stubs und sonstige Serverklassen, die vom Client bei Remote-Aufrufen benötigt werden) liegen nur z. T. im CLASSPATH des Clients. Alle Klassen, die der Server benötigt, liegen bei ihm lokal vor. Damit alles klappt, muss der Client einen SecurityManager verwenden und der Server muss beim Aufruf mit der Definition seiner Codebase gestartet werden. Außerdem muss diese Codebase für den Client zugänglich sein, üblicherweise durch einen HTTP-Webserver4. 3. Sowohl Client als auch Server besitzen nicht alle Klassen lokal, d.h., während des Programmablaufs müssen beide Seiten voneinander Klassendefinitionen laden. Daher muss jede Seite einen Security-Manager verwenden, beim Start eine Codebase definieren und es muss auf jedem Rechner ein HTTP-Webserver laufen, der die Daten zum Download anbietet. Die wesentlichen Ergänzungen zum Erstellen einer verteilten RMI-Anwendung sind somit das Anlegen eines Security-Managers und die Definition der Codebase.
37.5.1 Bereitstellen eines Security-Managers Um eine RMI-Anwendung mit einem Security-Manager zu versehen, verwendet man ganz einfach die Klasse RMISecurityManager (Package java.rmi): RMISecurityManager sec = new RMISecurityManager(); System.setSecurityManager(sec);
Die angelegte Instanz von RMISecurityManager wird anschließend über eine statische System-Methode gesetzt. Dies muss die erste Anweisung bzgl. RMI-Aktionen sein, damit es nicht zu einer SecurityException kommt. Nun weiß der neue Security-Manager aber noch nicht, was er der Anwendung, zu der er nun gehört, erlauben soll. Hierfür muss noch eine sogenannte Policy-Datei angelegt werden, in dem die Sicherheitspolitik festgelegt wird. 4
1032
oder ein FTP-Server oder ein Network-Filesystem etc.
Erstellen einer verteilten RMI-Anwendung
Da RMI-Anwendungen hinter den Kulissen über Sockets mit anderen Rechnern kommunizieren, müssen Sie deswegen in einer solchen Datei die Rechte für die Socketkommunikation setzen (mehr Details zum Thema Sicherheit und dem Einsatz von Policy-Dateien finden Sie in Kapitel 40.2). Hier ein Beispiel: // Datei alles.policy grant { permission java.security.AllPermission; };
29 30
Diese Datei gibt alle Rechte frei, was natürlich höchstens für Testzwecke akzeptabel ist. Für einen realistischen RMI-Einsatz sollte sich die Datei wirklich auf die Sockets beschränken:
31
// Datei rmi.policy grant { permission java.net.SocketPermission "*:1024","accept,connect,listen,resolve"; };
32
33
Die gezeigte Policy-Datei erlaubt eine Socket-Verbindung zu allen IP-Adressen ("*") und zu jedem Port größer/gleich Nummer 1024, sowohl sendend als auch empfangend. Wenn Sie es noch restriktiver haben wollen, können Sie natürlich auch eine konkrete IP-Adresse/Port angeben.
34
Beim Aufruf der Java-Anwendung gibt man die definierte Policy-Datei als Parameter an die JVM über die Systemeigenschaft (Property) java.security.policy weiter, z. B.:
35
java -Djava.security.policy=rmi.policy ZentralUhrClient
36
37.5.2 Definition der Codebase
37
Die Codebase bezeichnet den Ort, an dem die Class-Dateien zu finden sind. Es handelt sich dabei um einen URL, der ein Verzeichnis, einen FTP-Server oder – der übliche Fall – einen HTTP-Server festlegt. Der anzugebende URL besteht somit aus dem Protokoll, dem Rechnernamen, der Portnummer (falls notwendig) und dem Verzeichnisnamen (der mit '/' enden muss!) oder einem JAR-Archiv. Codebase
Erklärung
file://192.168.200.2/RMI/
Verzeichnis RMI auf Rechner 192.168.200.2
ftp://hase.mynet.de:8000/RMI/
Verzeichnis RMI über FTP-Server auf hase.mynet.de, Port 8000
http://hase.mynet.de/RMI/mein.jar
mein.jar im Verzeichnis RMI über Webserver auf hase.mynet.de, Standardport 80
38 39 Tabelle 37.3: CodebaseBeispiele
40 41 42
1033
Verteilte Anwendungen mit RMI
Die definierte Codebase muss natürlich für den gedachten Nutzer (z. B. einen RMI-Client) auch zugreifbar sein, d.h., es muss ein FTP- oder Webserver laufen bzw. bei file:// muss das Verzeichnis über einen Netzwerk-Fileserver (z. B. NFS) zugreifbar sein. Die Codebase wird beim Aufruf des Server-Programms, das Remote-Objekte zur Verfügung stellt, als Eigenschaft java.rmi.server.codebase der JVM mitgeteilt, z. B. java -Djava.rmi.server.codebase=http://hase.mynet.de: 8000/RMI/ ZentralUhrServer
Wir sind nun soweit, die parametrisierte Version unseres Uhrzeit-Services in eine verteilte Version umzuwandeln. Da in der Praxis bei einer verteilten RMI-Anwendung viele Kleinigkeiten schief laufen können, hier noch einmal das Prozedere im Einzelnen: 1. Definition der Remote-Schnittstelle und UhrArgumentSchnittstelle Keine Änderungen. 2. Implementierung der Remote-Schnittstelle Keine Änderungen. 3. Implementierung des Server-Programms Hier ändert sich neben der Versionsnummer nur wenig: Es wird eine Instanz von RMISecurityManager angelegt und dem Java-Laufzeitsystem bekannt gemacht: Listing 37.9: ZentralUhrServer.java import java.rmi.*; public class ZentralUhrServer { public static void main(String[] args) { System.out.println(); // Security-Manager erzeugen RMISecurityManager sec = new RMISecurityManager(); System.setSecurityManager(sec); try { // Objekt anlegen und anmelden ZentralUhr uhr = new ZentralUhr(); // Kurzform für rmi://localhost/ZentralUhrService" Naming.rebind("/ZentralUhrService", uhr); System.out.println(" ZentralUhrService laeuft..."); } catch(Exception e) { System.err.println(" ZentralUhrServer: Exception " + e); } } }
1034
Erstellen einer verteilten RMI-Anwendung
4. Implementierung des Client-Programms Der Client wird ebenfalls um einen Security-Manager ergänzt. Außerdem wird als Parameter der Namen des Rechners übergeben, auf dem der UhrService läuft. Hiermit wird dann bei Aufruf von lookup() ein voller URL für den Zugriff auf die RMI-Systemregistrierung des Servers definiert. Listing 37.10: ZentralUhrClient.java
29
import java.rmi.*; import java.util.Date; import java.text.DateFormat;
30
public class ZentralUhrClient { public static void main(String[] args) { System.out.println();
31
if(args.length != 1) { System.out.println(" Falsche Anzahl Argumente in Befehlszeile"); System.out.println(" Aufruf mit Servernamen (DNS oder IP)"); System.exit(0); }
32
33 34
// Servernamen merken String server = args[0];
35
// einen Security-Manager installieren RMISecurityManager sec = new RMISecurityManager(); System.setSecurityManager(sec);
36
try { // Anfrage an Registry stellen: ZentralUhrSchnittstelle uhr = (ZentralUhrSchnittstelle) Naming.lookup("rmi://" + server + "/ZentralUhrService");
37 38
// der Remote-Aufruf ohne Parameter Date aktZeit = uhr.aktuelleZeit(); DateFormat tf = DateFormat.getTimeInstance(DateFormat.LONG); System.out.println(" Client: Uhrzeit ist " + tf.format(aktZeit));
39 40
Dummy tmp = new Dummy(); tmp.setzeUhrzeit(new Date());
41 // der Remote-Aufruf mit Parameter (lokales Objekt) Dummy erg = (Dummy) uhr.aktuelleZeit(tmp); System.out.println(" + System.out.println(" +
42
Zeit in tmp ist: " tf.format(tmp.meineZeit)); Zeit in erg ist: " tf.format(erg.meineZeit));
1035
Verteilte Anwendungen mit RMI
} catch(Exception e) { System.err.println("Client: Exception " + e); } } } class Dummy implements UhrArgumentSchnittstelle { public Date meineZeit; public void setzeUhrzeit(Date arg) { meineZeit = arg; } }
5. Generierung von Stub/Skeleton-Klassen Nun werden, wie gehabt, alle .java Dateien kompiliert und dann rmic aufgerufen: rmic ZentralUhr
TIPP
Achtung: Der Einsatz von rmic ist nicht notwendig, wenn sowohl auf Clientals auch auf Serverseite eine Java-Version ab 1.5 (Java 5) zum Einsatz kommt (in diesem Fall wird die notwendige Stub/Skeleton-Information dynamisch zur Laufzeit mithilfe von Reflection generiert)! Wenn jedoch eine der beiden Seiten mit einer älteren Java-Version kompiliert worden ist, dann muss rmic eingesetzt werden. 6. Verteilung der notwendigen Dateien auf die beteiligten Rechner Damit liegen nun sieben Class-Dateien vor, die die RMI-Anwendung bilden. Man könnte sie sowohl auf dem (den) Client(s) als auch auf dem Server installieren. Dadurch stellt man sicher, dass es keine Probleme mit nicht vorhandenen Class-Dateien gibt oder mit nicht ladbaren Klassen, weil die Angaben der Codebase fehlen oder falsch sind. Diese Situation entspräche der im Abschnitt 37.3. Allerdings soll in diesem Beispiel der Code so verteilt werden, dass Client-Code auf dem Client liegt und Server-Code auf dem Server, damit wirkliche Ladevorgänge über das Netzwerk stattfinden. Der Servercode besteht aus den Dateien UhrArgumentSchnittstelle.class, ZentralUhrSchnittstelle.class, ZentralUhr.class und ZentralUhrServer.class sowie den Stubklassen ZentralUhr_Stub.class und ZentralUhr_Skel.class. Diese Dateien müssen auf einen Serverrechner transferiert werden, beispielsweise in das Verzeichnis /home/RMIServer eines Linux-Servers myserver. Der Clientcode besteht aus UhrArgumentSchnittstelle.class, ZentralUhrSchnittstelle.class, ZentralUhrClient.class und Dummy.class. Diese Dateien werden auf einem WinNT-Clientrechner NTclient im Verzeichnis c:\RMIClient deponiert. Außerdem brauchen Sie noch jeweils für Client und Server eine Policy-Datei mit den Sicherheitsbeschränkungen für den Security-Manager. Der Einfachheit halber werden für beide Seiten die gleichen Einstellungen vorgenom-
1036
Erstellen einer verteilten RMI-Anwendung
men: erlaube Aufbau und Empfang von Socket-Verbindungen von/zu jedem Rechner und Portnummer größer/gleich 1024: Listing 37.11: rmi.policy grant { permission java.net.SocketPermission "*:1024-", "accept,connect,listen,resolve"; };
29
Diese Datei muss ebenfalls in die entsprechenden Verzeichnisse auf myserver und NTclient kopiert werden.
30
7. Starten der Hilfsprogramme
31
Jetzt kann es losgehen. Zunächst wird auf myserver die RMI-Systemregistrierung durch Aufruf von rmiregistry gestartet. In der Konsole, in der rmiregistry aufgerufen wird, darf kein CLASSPATH definiert sein, in dem sich die Class-Dateien unserer Serveranwendung befinden. Ansonsten würde später ein anfragender Client von der RMI-Systemregistrierung nicht auf die richtige Codebase verwiesen und die Anfrage des Clients schlüge fehl. Aus dem gleichen Grund darf rmiregistry auch nicht im Verzeichnis der Class-Dateien aufgerufen werden (hier im Beispiel also /home/RMIserver).
32 HALT
33 34 35
Der nächste Schritt besteht im Starten des HTTP-Webservers auf unserem Server myserver. Falls Sie gerade keinen Webserver zur Hand haben, nehmen Sie den MiniWebServer aus Kapitel 37. Der Webserver sollte als Basisverzeichnis das Verzeichnis mit dem Servercode erhalten und einen freien Port. Bei Verwendung des MiniWebServers also:
36 37
java MiniWebServer /home/RMIserver 8000 Die gleiche Prozedur wiederholen Sie auf dem Client NTclient, z.B.
38
java MiniWebServer c:\RMIclient 7000 8. Starten der RMI-Anwendung
39
Nun wird es ernst. Zuerst starten Sie auf myserver das Serverprogramm. Beim Aufruf geben Sie der ausführenden JVM die zu verwendende PolicyDatei und die Codebase mit:
40
java -Djava.security.policy=rmi.policy -Djava.rmi.server.codebase=http://myserver:8000/ ZentralUhrServer
41
Falls Sie den MiniWebServer verwenden, können Sie in dessen Konsolenfenster nun schon einige Anfragen (u. a. nach dem Stub) auf die Codebase sehen, die von der RMI-Systemregistrierung stammen.
42
Jetzt können Sie (endlich!) auf NTclient den Client starten. Analog zum Serverprogramm geben Sie beim Aufruf die Policy-Datei und die Angabe der 1037
Verteilte Anwendungen mit RMI
Codebase der JVM mit und unserem Client als Parameter den Namen des Serverrechners: java -Djava.security.policy=rmi.policy -Djava.rmi.server.codebase=http://NTclient:7000/ ZentralUhrClient myserver
Wenn alles geklappt hat, sollten Sie die Uhrzeit-Ausgabe des Clientprogramms sehen! Abbildung 37.5: Die Beteiligung der Webserver bei der RMI-Anwendung
CLIENT
SERVER
Webserver
Webserver Stub
y
m
TIPP
TIPP
St
ub
m Du
ZentralUhrClient
Registrierung
ZentralUhrServer
Falls Ihre Version des RMI-Beispiels nicht funktioniert, gehen Sie bitte noch einmal sorgfältig alle Schritte durch. Achten Sie insbesondere darauf, dass rmiregistry richtig gestartet wird und dass die Angaben der Codebase (und die Basisverzeichnisse der Webserver richtig definiert sind). Und vergessen Sie bei der Codebase das abschließende '/' nicht. Weiterhin muss Ihr Netzwerk richtig konfiguriert sein (falls Domain-Namen zum Einsatz kommen: klappt DNS?, sind evtl. vorhandene Router richtig eingestellt?). Falls der Client-Aufruf unter Windows klappt, aber sehr lange dauert, dann liegt dies wahrscheinlich an einem nicht optimal konfigurierten DNS. Fügen Sie daher entsprechende Einträge in die Hosts-Datei hinzu (je nach System z. B. c:\winnt\system32\drivers\etc\hosts oder c:\windows\system32\drivers\etc\hosts oder c:\windows\hosts). Falls ein Rechner mehrere IP-Adressen besitzt, kann es vorkommen, dass Ihr Java-Programm die falsche verwendet. Setzen Sie dann beim Aufruf die Eigenschaft -Djava.rmi.server.hostname=xxx.xxx.xxx.xxx auf die richtigen IP-Adresse.
1038
Ergänzungen
37.6 Ergänzungen Nachdem Sie nun fundierte Grundlagen über RMI besitzen, wollen wir in diesem Abschnitt noch einige wichtige Ergänzungen besprechen.
37.6.1 Automatischer Start der Systemregistrierung
29
Bisher haben Sie eine Instanz der RMI-Systemregistrierung immer von Hand gestartet. Man kann dies auch automatisch über das Serverprogramm machen lassen. Hierzu dient die Klasse LocateRegistry (Paket java.rmi.registry) mit den statischen Methoden: Registry createRegistry(int port);
Erzeugt eine neue Systemregistrierung auf dem angegebenen port.
Registry getRegistry();
Liefert eine Referenz für eine auf dem Rechner schon laufende Systemregistrierung (mit Defaultport 1099).
Registry getRegistry(int port);
Liefert eine Referenz für eine auf dem Rechner schon laufende Systemregistrierung auf port.
30 Tabelle 37.4: Statische Methoden der Klasse LocateRegistry
31 32
33
Die Methoden werfen bei Problemen eine java.rmi.RemoteException.
34
Ein typischer Aufruf innerhalb eines Serverprogramms sieht dann folgendermaßen aus:
35
import java.rmi.registry.*; import java.rmi.*; // ...... Registry reg = null; try { reg = LocateRegistry.createRegistry(1099); } catch(RemoteException e) { System.err.println(" " + e.getMessage()); }
36 37 38
Wenn man also eine Systemregistrierung vom Serverprogramm aus starten kann, warum dann umständlich über rmiregistry vorgehen? Bei einem einzigen Serverprogramm ist nichts dagegen einzuwenden. Komplizierter und unübersichtlicher wird es allerdings, wenn mehrere Serverprogramme laufen. Dann muss man aufpassen, dass alle die gleiche Systemregistrierung verwenden (oder jede einen anderen Port, was aber für Clients dann umständlich ist) und nicht eine neue erzeugen, obwohl schon eine existiert. Es ist auch aufwendig herauszufinden, ob eine Systemregistrierung schon läuft (die obige Methode getRegistry() reicht dazu nicht, da sie nur eine Referenz liefert und nicht über einen echten Verbindungsaufbau testet, ob die Systemregistrierung tatsächlich läuft). Aus diesem Grund ist rmiregistry oft einfacher, handlicher und übersichtlicher.
39 40 41 42
1039
Verteilte Anwendungen mit RMI
37.6.2 Remote-Implementierung ohne UnicastRemoteObject Klassen, die eine remote-Schnittstelle implementieren, müssen von UnicastRemoteObject abgeleitet werden. Nun kann es natürlich vorkommen, dass die betreffende Klasse bereits von einer anderen direkten Basisklasse abgeleitet werden soll. Als Ausweg muss man dann im Prinzip zu Fuß vorgehen und selbst realisieren, was bei Ableitung von UnicastRemoteObject automatisch passiert, nämlich den Export des erzeugten Objekts nach außen. Dies erfolgt im Konstruktor des Objekts. Hierzu bietet UnicastRemoteObject (Paket java.rmi.server) die statischen Methoden exportObject(): Tabelle 37.5: Methoden von UnicastRemoteObjekt
RemoteStub exportObject(Remote obj);
Erzeugt einen RemoteStub für das gewünschte Objekt auf einem beliebigen Port (d.h., es wird an einen Socket gebunden).
RemoteStub exportObject(Remote obj, int port);
Erzeugt einen RemoteStub für das gewünschte Objekt auf port (d.h., es wird an einen Socket gebunden).
Wie üblich werfen die Methoden eine java.rmi.RemoteException. Im folgenden Beispiel sehen Sie eine Version von ZentralUhr, die nicht mehr von UnicastRemoteObject abgeleitet ist: public class ZentralUhr implements ZentralUhrSchnittstelle { // Standardkonstruktor public ZentralUhr() throws RemoteException { // die Instanz exportieren UnicastRemoteObject.exportObject(this); } // der Rest bleibt gleich }
Für die meisten Zwecke reicht dieser simple Aufruf von exportObject() schon aus, um die Verwendung von UnicastRemoteObject zu vermeiden. Wenn man allerdings ganz exakt arbeiten will, muss noch mehr getan werden, denn die Klasse UnicastRemoteObject überschreibt noch drei von Object geerbte Methoden: hashCode(), equals(), toString(). Gegebenenfalls müssen Sie diese Methoden auch implementieren.
1040
Inhalt
38 Applets 29 30
Applets sind Java-Programme, die in Webseiten (HTML-Dokumente) eingebettet werden können. Steuert ein Websurfer eine Webseite mit einem eingebetteten Applet an, wird das Applet-Programm zusammen mit dem HTML-Code der Webseite zum Rechner des Websurfers geschickt und dort vom Browser ausgeführt (vorausgesetzt, dieser hat das Java-Plug-In installiert).
31 32
Wenn Sie als Anbieter eines Webdokuments dieses um ein Java-Applet bereichern wollen, programmieren Sie das Applet, nehmen in Ihr HTMLDokument einen Verweis auf das Applet auf und speichern HTML-Dokument und Applet zusammen auf Ihrem Server.
33
Applets zu programmieren, ist für Java-Programmierer, die bereits erste Erfahrungen in der Entwicklung von GUI-Anwendungen gesammelt haben, allgemein keine Schwierigkeit. Es gibt jedoch einige Besonderheiten, die zu beachten sind und die in diesem Kapitel eingehender erörtert werden.
34
Applet bedeutet soviel wie »little application«.
36
38.1 Das Applet-Grundgerüst
35
TIPP
37
Die erste Entscheidung, die der Applet-Programmierer treffen muss, ist die Wahl einer geeigneten Basisklasse.
38
38.1.1 Die Basisklasse: Applet versus JApplet
39
Ein Applet muss eine Hauptklasse besitzen, die von java.applet.Applet abgeleitet ist. Die Basisklasse Applet vererbt Ihrer Klasse eine Reihe von Standardmethoden, die jedes Applet besitzen muss und die von der Virtual Machine des Browser-Plug-Ins aufgerufen werden. Anstatt Ihre Applets direkt von java.applet.Applet abzuleiten, können Sie als direkte Basisklasse aber auch javax.swing.JApplet verwenden. JApplet ist von Applet abgeleitet und hat den Vorteil, dass sie als Container für Swing-Elemente dienen kann. Außerdem können JApplet-Applets mit Menüs ausgestattet werden.
40 41 42
1041
Index
Die Entscheidung zwischen Applet und JApplet ist aber nicht nur eine Entscheidung zwischen AWT und Swing, sondern auch zwischen Rückwärts-
Applets
kompatibilität und Fortschritt. JApplet und Swing gibt es erst seit Java 1.2. Ältere, oder sollte man sagen »angestaubte« Browser können Applets, die von JApplet abgeleitet sind, daher nicht zur Ausführung bringen. Sollte man deswegen Applets grundsätzlich von Applet ableiten? Nein! Zum einen verwenden die meisten Websurfer mittlerweile Browser, die Swing unterstützen. Zum anderen kann man nicht nur rückwärts gewandt denken. Und schließlich ist die Beschränkung auf Applet keine Garantie, dass Ihre Applets auch in den Browsern Ihrer Webbesucher angezeigt werden (siehe Kasten). Im Folgenden sehen Sie zuerst ein Beispiel für ein AWT-Applet und danach erfahren Sie, wie das gleiche Applet als Swing-Applet programmiert werden kann. Browser, Plug-Ins und das Klassenformat EXKURS
Wie Sie wissen, übersetzt der Java-Compiler Ihre Klassendefinitionen in Bytecode, den er in Class-Dateien speichert. Dabei benutzt er ein spezielles Klassenformat. Die Versionsnummer dieses Formats hinterlegt er mit den Klassen in den Class-Dateien. Die Virtual Machine, die die Class-Dateien lädt und ausführt, prüft zuerst, ob sie das vorliegende Klassenformat kennt. Wenn ja, lädt sie die Klassen und führt sie aus. Ist ihr das Klassenformat unbekannt, erzeugt sie einen Fehler der Art »java.lang.ClassFormatError; Bad major version number«. Wenn Sie Ihre Applets mit dem neuesten JDK erstellen, wird es natürlich immer Websurfer geben, in deren Browser ältere Plug-Ins integriert sind, die das von Ihrem Java-Compiler erzeugte Klassenformat nicht verarbeiten können. Da nutzt es auch nichts, die Applet-Klasse von Applet statt von JApplet abzuleiten. Was Sie tun können: –
– –
1042
Sie verwenden das neue JDK und setzen HTML-Code auf, der den Websurfer automatisch zur Installation des aktuellen Plug-Ins auffordert, wenn sein Plug-In Ihr Applet nicht ausführen kann. (Beim Aufsetzen des HTML-Codes hilft Ihnen das JDK-Tool htmlConverter.) Sie verwenden eine etwas ältere JDK-Version, die aber bereits Swing unterstützt. Sie setzen auf optimale Abwärtskompatibilität, verwenden Applet als Basisklasse und erstellen Ihre Applets mit einer 1.1-Version des JDK. (Siehe auch Hinweise zu Cross-Compiling im Anhang zu den JDKTools.)
Das Applet-Grundgerüst
38.1.2 Der Applet-Lebenszyklus Unabhängig davon, ob Sie Applet oder JApplet als direkte Basisklasse angeben – Ihre abgeleitete Applet-Klasse erbt fünf wichtige Methoden namens init(), start(), paint(), stop() und destroy(). Diese Methoden muss jedes Applet besitzen, denn der Browser, der das Applet ausführt, ruft diese Methoden zu verschiedenen Zeitpunkten im Lebenszyklus des Applets auf: ■ ■ ■ ■
■
29
init() – wird aufgerufen, nachdem der Browser das Applet geladen hat.1 start() – wird aufgerufen, wenn der Browser das Applet startet. paint() – wird aufgerufen, wenn der Browser möchte, dass sich das Applet ganz oder teilweise neu zeichnet. stop() – wird aufgerufen, wenn der Browser das Applet anhält, (beispielsweise weil der Anwender die Webseite verlassen und zu einer anderen Webseite gewechselt hat). destroy() – wird aufgerufen, bevor der Browser das Applet aus dem Arbeitsspeicher entfernt.
Welche Aktionen genau, welche Applet-Methoden aufrufen, variiert ein wenig von Browser zu Browser und von Version zu Version. Die neueren Versionen des Internet Explorers und des Netscape-Browsers rufen beispielsweise bei jedem Verlassen der Webseite, die das Applet enthält, stop() und destroy() auf und führen bei einer Rückkehr auf die Webseite Konstruktor, init(), start() und paint() aus. Bei einem Aktualisieren exerzieren Sie die gesamte Palette von stop() bis start() durch.
30 31 32
33 34 TIPP
35
36
Die Methoden sind nicht abstrakt, müssen also nicht überschrieben werden. Sie können Sie aber überschreiben, um das Verhalten Ihres Applets anzupassen.
37
38.1.3 AWT-Applets
38
Das folgende Listing erzeugt ein einfaches Applet, das eine Reihe von wichtigen Techniken demonstriert:
39
■ ■ ■ ■ ■
die Überschreibung der Applet-Methoden Einbau von Steuerelementen Reagieren auf Aktualisierung der Webseite Einfärben des Applet-Hintergrunds Zeichnen in Applets
40 41 42
1
Vor init() wird selbstverständlich noch der Konstruktor der Applet-Klasse aufgerufen und ausgeführt. Applets definieren in der Regel jedoch keinen eigenen Konstruktor, sondern erledigen anfallende Initialisierungsarbeiten in init().
1043
Applets
Listing 38.1: Grundgeruest.java – Vorstellung grundlegender Applet-Techniken 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
import java.awt.*; import java.awt.event.*; import java.util.Random; public class Grundgeruest extends java.applet.Applet { Color hintergrundfarbe; Label textfeld; Random generator; public void init() { setLayout(null); textfeld = new Label("Hallo von Applet"); textfeld.setBounds(10, 10, 200, 20); textfeld.setBackground(Color.WHITE); add(textfeld); generator = new Random(); } public void start() { hintergrundfarbe = new Color(generator.nextInt(255), generator.nextInt(255), generator.nextInt(255)); } public void stop() { } public void paint(Graphics g) { setBackground(hintergrundfarbe); g.drawRect(5, 5, 210, 30); } public void destroy() { } }
Wie Sie Zeile 5 entnehmen können, handelt es sich um ein AWT-Applet. Sämtliche Applet-Methoden sind überschrieben, auch wenn nur drei Code enthalten. (Die Überschreibung von stop() und destroy() hätte ganz weggelassen werden können, aber so können Sie dem Listing auf einen Blick die Signaturen aller Applet-Methoden entnehmen.) In der init()-Methode wird ein Textfeld erzeugt und mithilfe von setBounds() an die Position (10,10) gesetzt. Da AWT-Applets standardmäßig den FlowLayout-Manager verwenden, wird in Zeile 12 das Layout-Managing auf null gesetzt (gestattet pixelgenaue Positionierung). Mithilfe der Container-Methode add() wird das Textfeld in das Applet eingebettet (java.applet.Applet ist von Container abgeleitet). 1044
Das Applet-Grundgerüst
In der start()-Methode wird zufällig eine Hintergrundfarbe ausgewählt. Jedes Mal, wenn der Browser die start()-Methode aufruft (etwa weil der Anwender die Webseite aktualisieren lässt), wird eine neue Farbe festgelegt. In der paint()-Methode wird der Hintergrund des Applets eingefärbt. Da die Applet-Klasse indirekt von Component abgeleitet ist, braucht hierzu nur die Component-Methode setBackground() aufgerufen zu werden. Schließlich zeichnet die Methode noch einen Rahmen um das Textfeld.
29 Abbildung 38.1: Das GrundgeruestApplet
30 31 32
33 34 35 38.1.4 Kompilierung und Ausführung
36
Um ein Applet auf Ihrem Computer zu erstellen und auszuführen, gehen Sie anfangs genauso wie bei der Erstellung einer Konsolenanwendung vor:
37
1.
Rufen Sie einen beliebigen Texteditor auf, mit dem Sie Ihren Quelltext als ASCII-Text abspeichern können. 2. Öffnen Sie eine neue Datei, tippen Sie Ihren Quelltext ein und speichern Sie die Datei unter einem passenden Namen mit der Dateiextension .java – beispielsweise: Speichern als Grundgeruest.java (für das Applet aus dem vorangehenden Abschnitt) 3. Kompilieren Sie das Applet. Prompt:> javac Grundgeruest.java
38 39 40
Bevor Sie das Applet ausführen und austesten können, müssen Sie noch eine zugehörige HTML-Seite aufsetzen, die das Applet aufruft.
41
4. Legen Sie mit Ihrem Editor eine neue Textdatei für das HTML-Dokument an, in die Sie folgenden Code eingeben:
42
1045
Applets
Erstes Applet
Webseite ruft Applet. Bitte antworten.
Eingebettet wird das Applet mit dem Tag . Der Parameter code gibt den Namen der Class-Datei des Applets an; width und height teilen dem Browser mit, wie groß die Anzeigefläche für das Applet sein soll. 5. Speichern Sie die Datei unter einem beliebigen Namen, aber mit der Extension .html (z. B. Grundgeruest.html) und in dem gleichen Verzeichnis wie das Java-Applet. Speichern als Grundgeruest.html 6. Laden Sie die HTML-Datei für erste Tests in den AppletViewer: Prompt:> appletviewer Grundgeruest.html oder in Ihren Browser. Abbildung 38.2: Applet im AppletViewer
38.1.5 Anzeige im Browser Damit ein Applet in einem Browser angezeigt werden kann, muss ■
1046
der Browser Java unterstützen Die großen Browser unterstützen mittlerweile alle Java. So verwundert es nicht, dass nach einer unabhängigen Umfrage 90% der InternetAnwender einen java-fähigen Browser verwenden.
Das Applet-Grundgerüst
■
■
der Browser das Format der Bytecode-Klassen lesen können Schwierigkeiten treten immer dann auf, wenn die Anwender alte PlugIns verwenden, die das Klassenformat, in dem Sie Ihre Applets erstellt haben, nicht verstehen. der Browser die Class-Dateien des Applets (sowie sonstige Dateien, die das Applet lädt) finden Dies können Sie durch korrekte Angaben im HTML-Code sicherstellen.
Um nicht-javafähige Browser besser zu unterstützen, können Sie das -Tag um ein alt-Attribut erweitern, in dem Sie einen alternativen Text angeben, der statt des Applets angezeigt wird:
29 30 TIPP
31
alt="Ihr Browser ist nicht java-fähig.">
32
33
HtmlConverter Gemäß der aktuellen HTML-Spezifikation gilt das Applet-Tag als veraltet. Zur Einbettung der Applets sollte stattdessen das -Tag verwendet werden. Leider wird dieses von den verschiedenen Browsern in unterschiedlicher Weise unterstützt.
34 35
Mit dem JDK-Tool HtmlConverter können Sie Ihre HTML-Seiten ohne große Mühe so weit es geht an den aktuellen HTML-Standard sowie die Anforderungen der wichtigsten Browser anpassen und gleichzeitig dafür sorgen, dass Websurfer, die Ihre Applets nicht ausführen können, weil sie veraltete Browser-Plug-Ins verwenden, zur Sun-Downloadseite für das aktuelle PlugIn geführt werden.
36 37
Alles was Sie tun müssen, ist Den HTML-Code wie oben beschrieben mit dem Applet-Tag aufsetzen. Das Tool HtmlConverter aus dem Bin-Verzeichnis Ihrer JDK-Installation aufrufen. (Sie können dies über die Konsole oder über den Windows Explorer tun – HtmlConverter ist eine Swing-Anwendung.) 3. Im Fenster die zu konvertierende HTML-Datei auswählen. 4. Die gewünschte Java-Version auswählen und 5. auf KONVERTIEREN drücken.
38
Die resultierende HTML-Datei enthält ein HTML-konformes -Tag, das beispielsweise vom Internet Explorer verstanden wird, mit einem eingeschlossenen -Tag für den Netscape-Browser. Ihr altes -Tag wurde auskommentiert. (Achtung! Wenn Sie diese HTML-Datei an den AppletViewer übergeben, erscheinen zwei Applet-Fenster, weil der AppletViewer zwei eingebettete Applets in der Webseite findet.)
41
1. 2.
39 40
42
1047
Applets
Abbildung 38.3: HtmlConverter
TIPP
Leider gibt es immer wieder auch Fälle, wo selbst der von HtmlConverter generierte HTML-Code es nicht schafft, ein Applet zur Anzeige zu bringen. Sofern dies nicht an falsch angegebenen Class-Dateinamen oder Pfaden liegt, versuchen Sie es mit dem guten alten -Tag, das immer noch breite Unterstützung findet. Auf jeden Fall sollten Sie Ihre Applets und Webseiten ausführlich testen lassen. Die Applet-Testumgebung Verwenden Sie für erste Tests den AppletViewer (auch wenn dieser gelegentlich Fehlfunktionen zeigt) und für abschließende Tests die gängigen Browser. Wenn Sie Ihre Applets in Browsern testen, kann es passieren, dass Sie im Browser nicht die aktuelle Version des Applets angezeigt bekommen, weil der Browser die Class-Datei des Applets gecacht hat. Lesen Sie in der Hilfe Ihres Browser nach, wie Sie Caching verhindern können. Wenn Sie mehrere JDK-Versionen oder Plug-Ins installiert haben, können Sie über das Java-Plug-In-Dialogfeld (Aufruf unter Windows über die Systemsteuerung) auswählen, welches Plug-In verwendet werden soll. Wenn Sie möchten, können Sie während des Testens Debug-Informationen mit System.err.println() auf die Java-Konsole des Browsers ausgeben. Dazu müssen Sie die Unterstützung für die Konsole im Java-Plug-In-Bedienungsfeld (Seite ERWEITERT ab Version 1.5) und gegebenenfalls auch in den Optionen Ihres Browsers aktivieren.
1048
Das Applet-Grundgerüst
Abbildung 38.4: Das Java-Plug-InBedienungsfeld
29 30 31 32
33 34 35
36 Abbildung 38.5: Die Java-Konsole (mit der Ausgabe: System.err. println ("Diese Ausgabe erscheint auf der java-Konsole");)
37
38 39 40 41 42
1049
Applets
38.2 Swing-Applets Gegenüber AWT-Applets müssen Sie bei Swing-Applets auf folgende Punkte achten: ■ ■ ■ ■
Ableitung von javax.swing.JApplet Gezeichnet wird in JPanel (überschreiben von paintComponent()) Steuerelemente werden in ContentPane eingefügt (nicht direkt in JApplet-Container) Standardlayout ist das BorderLayout
Listing 38.2: GrundgeruestSwing.java – Vorstellung grundlegender Applet-Techniken import import import import
java.awt.*; java.awt.event.*; java.util.Random; javax.swing.*;
public class GrundgeruestSwing extends JApplet { Color hintergrundfarbe; JLabel textfeld; Random generator; public void init() { getContentPane().add(new Leinwand()); generator = new Random(); } public void start() { hintergrundfarbe = new Color(generator.nextInt(255), generator.nextInt(255), generator.nextInt(255)); }
class Leinwand extends JPanel { JLabel textfeld; Leinwand() { setLayout(null); textfeld = new JLabel("Hallo vom JApplet"); textfeld.setBounds(10, 10, 200, 20); textfeld.setBackground(Color.WHITE); add(textfeld); } public void paintComponent(Graphics g) { super.paintComponent(g); setBackground(hintergrundfarbe);
1050
Applets und Threads
g.draw3DRect(5, 5, 210, 30, true); } } }
38.3 Applets und Threads
29
Wenn Sie in einem Applet Threads oder periodische Timer einsetzen, sollten Sie diese in der stop()-Methode anhalten oder beenden und in start() weiterführen oder neu erzeugen.
30
Das folgende Beispiel implementiert ein Applet, das die aktuelle Uhrzeit anzeigt. Es erzeugt zwei Threads: den Hauptthread, der unter anderem für die Darstellung des Applets verantwortlich ist, und einen zweiten Thread (die run()-Methode des Applets), der die aktuelle Zeit abfragt.
31 32
Listing 38.3: Uhrzeit.java – als Thread realisierte Zeitanzeige 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
import import import import
java.util.*; java.text.*; java.awt.*; javax.swing.*;
// für die Klasse Date // für die Klasse DataFormat
33 34
public class Uhrzeit extends JApplet implements Runnable { String aktZeit; DateFormat formatierer; Thread zeit = null; // Referenz auf Thread Font anzeigeFont;
35
36
class Leinwand extends JPanel { public void paintComponent(Graphics g) { super.paintComponent(g); getContentPane().setBackground(Color.WHITE);
37
38
// die aktuelle Uhrzeit anzeigen g.setFont(anzeigeFont); g.setColor(Color.BLUE); g.drawString(aktZeit,20,45);
39
} }
40
public void init() { anzeigeFont = new Font("Serif",Font.BOLD,22); formatierer = DateFormat.getTimeInstance(); aktZeit = formatierer.format(new Date());
41
// einen zusätzlichen Thread anlegen zeit = new Thread(this); zeit.start();
42
getContentPane().add(new Leinwand());
1051
Applets
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 }
} public void start() { if(zeit == null) { zeit = new Thread(this); zeit.start(); } } public void stop() { if(zeit != null) zeit = null; } public void destroy() { if(zeit != null) zeit = null; }
// die run()-Methode des Threads public void run() { while(zeit == Thread.currentThread()) { aktZeit = formatierer.format(new Date()); getContentPane().repaint(); try { zeit.sleep(1000); } catch(InterruptedException e) { return; } } }
Für Applets gibt es zwei Möglichkeiten, Threads zu erzeugen: entweder es wird eine eigene Thread-Klasse abgeleitet oder das Applet implementiert die Schnittstelle Runnable (Zeile 6) und definiert eine eigene run()-Methode (Zeilen 56-67). Allerdings gibt es dann erst einmal keine Möglichkeit, die Ausführung des internen Threads zu steuern. Aus diesem Grund deklariert die obige Applet-Klasse ein Thread-Feld zeit und speichert in diesem eine Referenz auf den eigenen, internen Thread (Zeilen 31 und 38). Diese Referenz wird nicht nur in den start()- und stop()-Methoden zum Zugriff auf den Thread genutzt, sondern dient auch dem Thread selbst als Indikator, wann er sich selbst beenden soll (Vergleich mit Thread.currentThread() in der while-Schleife der run()-Methode, Zeile 57). In der Methode stop() wird der Zeitthread beendet, in start() wird er neu erzeugt und gestartet. (Letzteres schein unnötig, da der Thread ja bereits in init() erzeugt und gestartet wird, aber denken Sie daran, dass nicht alle Browser bei einer Aktualisierung stets init() und start() ausführen!)
1052
Applets und Multimedia
38.4 Applets und Multimedia Bilder und Sounddateien können Sie mit den Methoden getImage() und getAudioClip() laden.
38.4.1 Bilddateien laden Zum Laden von Bilddateien stehen Ihnen in Applets folgende Methoden zur Verfügung: Image getImage(URL url) Image getImage(URL url, String name)
Beide Methoden erwarten einen URL bzw. einen URL und eine dazu relative Pfadangabe. Der URL muss auf den Server des Applets verweisen und kann von getCodeBase() angefordert werden. Beide Methoden kehren direkt vom Aufruf zurück: das Bild wird also noch nicht geladen, sondern erst beim Versuch, das Bild auf dem Bildschirm anzuzeigen. Dies verursacht dann meist eine lästige und merkliche Wartezeit, weshalb man das Laden durch Verwenden eines MediaTracker-Objekts direkt nach dem Aufruf von getImage() erzwingen sollte.
Java unterstützt nur bestimmte Bildformate wie z. B. GIF, JPEG und PNG
29 30 31 32
33
Der typische Ladevorgang eines Bildes innerhalb eines Applets sieht daher folgendermaßen aus:
34
// Laden eines Bildes try { Image bild = getImage(getCodeBase(), "Bild.gif"); MediaTracker tracker = new MediaTracker(this);
35
36
// das zu ladende Bild als ID 0 registrieren tracker.addImage(bild,0);
37
// warten, bis das Bild geladen ist tracker.waitForID(0); } catch(Exception e) { System.err.println(e.getMessage()); }
38
Damit ist das Bild geladen und kann mit der drawImage()-Methode der Graphics-Klasse angezeigt werden.
39 40
38.4.2 Sounddateien laden Noch einfacher als der Umgang mit Bildern ist die Verarbeitung von Sounddateien. In Analogie zur Verarbeitung von Bilddateien benötigen Sie dazu erst einmal eine passende Klasseninstanz, in die Sie den Inhalt der Sounddatei laden können. Nur dass das Ziel diesmal kein Objekt der Klasse Image ist, sondern ein Objekt einer Klasse, die die AudioClip-Schnittstelle implementiert. Die Bereitstellung dieses Objekts und das Laden der Sounddatei besorgt die Methode getAudioClip(), die dafür als Parameter einen URL und den Namen der zu ladenden Sounddatei benötigt.
Java unterstützt nur bestimmte Soundformate wie z. B. WAV, MIDI, AIFF, RMF und AU
1053
41 42
Applets
Der typische Aufruf zum Laden einer Sounddatei sieht damit wie folgt aus: AudioClip sound = getAudioClip(getCodeBase(),"ein_toller_Sound.au");
Im Gegensatz zum Laden von Bildern kehrt der Aufruf von getAudioClip() erst zurück, wenn die Datei vollständig geladen worden ist. Ein MediaTracker zur Überwachung ist daher unnötig. Falls die angegebene Datei nicht geladen werden konnte (weil sie zum Beispiel nicht existiert), wird der Wert null zurückgegeben. Vor dem Abspielen der Sounddatei sollte man dies nutzen, um sicherzugehen, dass die Sounddatei auch korrekt geladen wurde. Zum Abspielen der Sounddateien stehen die in der AudioClip-Schnittstelle definierten Methoden zur Verfügung: sound.play() // spielt den Inhalt bis zum Dateiende ab sound.stop() // beendet das Abspielen sound.loop() // spielt die Sounddatei in einer Endlosschleife ab
TIPP
Beachten Sie bitte, dass bei Einsatz der loop()-Methode das Abspielen explizit durch stop() beendet werden muss. Ansonsten kann es passieren, dass Applet oder Anwendung schon beendet sind, aber die Musik munter weiterspielt! Interessant ist auch, dass die Java-Laufzeitumgebung das Abspielen einer Sounddatei in einen internen Thread packt! Dadurch ist es der loop()Methode möglich, ein Applet während seiner gesamten Lebensdauer mit Hintergrund-Musik zu unterlegen, ohne den Hauptthread des Applets zu blockieren.
38.5 Parameterübergabe an Applets Manchmal ist es wünschenswert, einem Applet, das in ein HTML-Dokument eingebettet ist, gewisse Parameter mitzugeben – beispielsweise die Abmaße des Applets im Browser oder irgendwelche zu verarbeitenden Daten. Hierzu dient das HTML-Tag . Definition der Parameter im HTML-Code Im HTML-Code fügen Sie für jeden Parameter, den Sie an das Applet übergeben wollen, ein -Tag in den Aufruf des Applets ein.
Jeder Parameter besteht aus einem Name/Wert-Paar, das im -Tag durch die Attribute name und value spezifiziert wird. name dient der Identifizierung des Parameters, value gibt den Wert an, der für den Parameter an das Applet übergeben wird.
1054
Parameterübergabe an Applets
Parameter im Applet abfragen – getParameter() Der zweite Schritt der Parameterübergabe ist das Auswerten der Parameter im Applet. Der Zugriff auf die vom HTML-Dokument übergebenen Parameter erfolgt in der init()-Methode des Applets. String parameter; int breite, hoehe;
29
parameter = getParameter("BREITE"); breite = Integer.parseInt(parameter);
30
parameter = getParameter("HOEHE"); hohe = Integer.parseInt(parameter);
31
Die Applet-Methode, mit der Sie die einzelnen Parameter vom HTML-Dokument abfragen können, heißt getParameter(). Die Methode getParameter() erwartet als Argument den Namen des AppletParameters, der von der HTML-Seite übergeben wird, und liefert dann den Wert dieses Applets-Parameters als String zurück. Dieser muss gegebenenfalls in einen anderen elementaren Typ umgewandelt werden.
32
Falls Sie neben der init()-Methode auch den Konstruktor des Applets zu dessen Initialisierung nutzen: Die Methode getParameter() kann nicht im Konstruktor verwendet werden, da Methoden von Klassen erst nach Erzeugung der Objekte zur Verfügung stehen.
34
Das folgende, etwas umfangreichere AWT-Applet-Beispiel implementiert einen zellulären Automaten2, den der Anwender über die linke und rechte Maustaste steuern kann: ■ ■
33
TIPP
35 Beispiel
36
Mit der rechten Maustaste kann die Simulation gestoppt und gestartet werden. Mit der linken Maustaste kann der Anwender weitere »lebende« Punkte markieren.
37
38
Was das Applet für uns im Moment interessant macht, ist der Umstand, dass es mit drei verschiedenen Startmustern initialisiert werden kann. Welches Muster es verwendet, entnimmt es einem Applet-Parameter namens SEED (siehe letzten beiden Zeilen der init()-Methode).
39
Listing 38.4: Life.java – Zellulärer Automat
40
import java.awt.*; import java.awt.event.*; import javax.swing.Timer;
41 42
public class Life extends java.applet.Applet { final int INIT = 0; 2
Was zelluläre Automaten sind und wie sie funktionieren, ist in der HTML-Datei zu dem Applet beschrieben.
1055
Applets
final int ZEILEN = 50; final int SPALTEN = 50; int skalierungX = 1; int skalierungY = 1; java.util.Random generator = new java.util.Random(); int pause = 1000; ActionListener ausfuehrer; Timer taktgeber; int[][] feld = new int[ZEILEN][SPALTEN]; int[][] tmp = new int[ZEILEN][SPALTEN];
class CMausAdapter extends MouseAdapter { int z, s; public void mouseClicked(MouseEvent e) { if (e.getButton() == MouseEvent.BUTTON1) { // Zelle setzen s = (int) Math.floor(e.getX()/skalierungX); z = (int) Math.floor(e.getY()/skalierungY); if( s >= SPALTEN || z >= ZEILEN) return; switch (feld[z][s]) { case 0: feld[z][s] = 1; break; case 1: feld[z][s] = 0; break; } repaint(); } else if (e.getButton() == MouseEvent.BUTTON3) { // Simulation starten/stoppen if (taktgeber.isRunning() == true) taktgeber.stop(); else taktgeber.start(); } else if (e.getButton() == MouseEvent.BUTTON2) { // Simulation stoppen if (taktgeber.isRunning() == true) taktgeber.stop(); for (int z = 0; z < ZEILEN; ++z) for (int s = 0; s < SPALTEN; ++s) feld[z][s] = 0;
repaint(); }
1056
Parameterübergabe an Applets
} }
public void init() { setBackground(Color.WHITE); skalierungX = (int) Math.floor(getWidth()/SPALTEN); skalierungY = (int) Math.floor(getHeight()/ZEILEN);
29
for (int z = 0; z < ZEILEN; ++z) for (int s = 0; s < SPALTEN; ++s) feld[z][s] = 0;
30 31
addMouseListener(new CMausAdapter()); ActionListener ausfuehrer = new ActionListener() { public void actionPerformed(ActionEvent evt) { caTransformieren(); repaint(); } }; taktgeber = new Timer(pause, ausfuehrer);
32
33 34
String param = getParameter("SEED"); caInitialisieren(Integer.parseInt(param));
35
} public void start() { }
36
public void stop() { if (taktgeber.isRunning() == true) taktgeber.stop(); }
37
38
private void feldZeichnen(int z, int s, Graphics g) { switch (feld[z][s]) { case 0 : g.setColor(Color.WHITE); break; case 1 : g.setColor(Color.GREEN); break; default : g.setColor(Color.BLACK); break; } g.fillRect(s*skalierungX,z*skalierungY,skalierungX,skalierungY); }
39 40 41
public void paint(Graphics g) { super.paint(g); for (int z = 0; z < ZEILEN; ++z) for (int s = 0; s < SPALTEN; ++s) { feldZeichnen(z,s,g);
42
1057
Applets
} }
private void caInitialisieren(int figur) { switch(figur) { case 0 : for(int i = 0; i < 500; ++i) feld[generator.nextInt(50)][generator.nextInt(50)] = 1; break; case 1 : feld[10][40] = 1; feld[10][41] = 1; feld[10][42] = 1; feld[11][40] = 1; feld[11][41] = 1; feld[11][42] = 1; feld[12][40] = 1; feld[12][41] = 1; feld[12][42] = 1; feld[13][41] = 1; feld[14][41] = 1; feld[15][41] = 1; feld[13][39] = 1; feld[13][43] = 1; feld[14][40] = 1; feld[14][42] = 1; feld[16][40] = 1; feld[16][42] = 1; feld[17][39] = 1; feld[17][43] = 1; feld[18][38] = 1; feld[18][39] = 1; feld[18][43] = 1; feld[18][44] = 1; break; default: feld[ 8][10] = 1; feld[ 9][ 9] = 1; feld[ 9][10] = 1; feld[ 9][11] = 1; feld[10][ 8] = 1; feld[10][ 9] = 1; feld[10][11] = 1; feld[10][12] = 1; feld[11][ 9] = 1; feld[11][10] = 1; feld[11][11] = 1; feld[12][10] = 1; break; } } private void caTransformieren() { int nachbarn; int n, m;
1058
Parameterübergabe an Applets
for (int z = 0; z < ZEILEN; ++z) for (int s = 0; s < SPALTEN; ++s) { // Anzahl Nachbarn berechnen nachbarn = 0; for(n = z-1; n = 0) if(feld[n][m] == 1 && !(n == z && m == s)) ++nachbarn; // Status ändern switch(nachbarn) { case 0: case 1: tmp[z][s] break; case 2: tmp[z][s] break; case 3: tmp[z][s] break; case 4: case 5: case 6: case 7: case 8: tmp[z][s] break; }
29 30 31
= 0; = feld[z][s];
32
= 1;
33 34 = 0;
35
}
36
for (int z = 0; z < ZEILEN; ++z) for (int s = 0; s < SPALTEN; ++s) feld[z][s] = tmp[z][s]; }
37
}
Das zu dem Applet mitgelieferte HTML-Dokument enthält eine kurze Einführung in die Funktionsweise der zellulären Automaten und eine Bedienungsanleitung. Dazwischen werden drei Instanzen des Applets eingebettet und mit unterschiedlichen Anfangswerten initialisiert:
38 39
|
40 41 42
1059
Applets
| |
TIPP
Die mitgelieferte HTML-Datei verwendet noch das -Tag. Wenn Sie möchten, können Sie diese aber natürlich mithilfe von HtmlConverter in -Tags umwandeln (siehe Abschnitt »HtmlConverter«).
Abbildung 38.6: Das Life-Applet in dreifacher Ausführung
38.6 Applets und JAR-Archive Komplexere Applets benötigen meistens zusätzliche Dateien, z. B. Bilddateien im GIF-Format oder weitere Class-Dateien von Hilfsklassen. Dies kann die Ladezeit des Applets stark erhöhen, weil die Datenmenge recht groß werden kann und der Browser für jede Komponente eine neue HTTP-Verbindung aufbauen muss, was recht langsam ist.
1060
Applets und die Sicherheit
Abhilfe bieten hier die JAR-Archive, die eine oder mehrere andere Dateien in komprimierter Form enthalten.
JAR-Dateien sind Archive
Um JAR-Archive erzeugen und verwalten zu können, enthält das JDK ein spezielles Programm, das jar heißt und ein reines Kommandozeilenprogramm ist. Die wichtigsten Kommandos sind: jar cf archivname datei1 datei2 ...
29
Erzeugt ein JAR-Archiv und fügt die genannten Dateien ein. jar tf archivname
30
Gibt das Inhaltsverzeichnis des angegebenen JAR-Archivs aus. jar xf archivname datei
31
Extrahiert die angegebene Datei aus dem Archiv. Um also beispielsweise ein Applet DasApplet.java zusammen mit den GIFDateien bild1.gif und bild2.gif in ein JAR-Archiv zu packen, müssen Sie zunächst ganz normal das Applet in Bytecode kompilieren und dann die Class-Datei und die GIFs archivieren:
32
33
jar cf beispiel.jar DApplet.class bild1.gif bild2.gif
Der Aufruf des Applets im JAR-Format innerhalb des HTML-Dokuments unterscheidet sich kaum vom Aufruf eines normalen Applets, lediglich ein zusätzliches Attribut zum Bekanntmachen des Archivs muss hinzugefügt werden:
34 35
36 37
38.7 Applets und die Sicherheit
38
Sie haben wahrscheinlich schon oft darüber gelesen, dass sich in der Computerwelt und insbesondere im Internet viele üble Gesellen herumtreiben wie beispielsweise Makroviren, Bootviren, Trojanische Pferde und etliche weitere Varianten. Vielleicht hatten Sie sogar schon Kontakt mit diesen Kreaturen und mussten deshalb Systemabstürze, formatierte Festplatten oder vielleicht sogar finanzielle Verluste in Kauf nehmen, da jemand mit Ihrem ausgespähten Passwort bei Ihrem Internet-Provider online gewesen ist. Daher sollte man gegenüber Programmen und Dateien, die man von anderen erhält, ein gesundes Maß an Misstrauen an den Tag legen und diese vor Gebrauch mit einem Virenscanner testen. Sicherlich sind Sie vernünftig und machen das sowieso immer! ;-)
39 40 41 42
Beim Herunterladen von Applets ist die Lage ähnlich, ja vielleicht sogar noch schlimmer. Wenn Sie einige Stunden im Internet herumgesurft sind und dabei Dutzende von Seiten besucht haben, sind auch unzählige Applets automatisch vom Browser auf Ihren Rechner geladen und ausgeführt wor1061
Applets
den, d.h., wildfremde Programme sind auf dem Rechner gestartet worden! Und das auch noch ohne vorherige Kontrolle durch einen Virenscanner! Ist das nicht hochgradig leichtsinnig? Im Prinzip muss die Antwort ja lauten und daher gibt es umfangreiche Sicherheitsmechanismen für Applets. Beim Laden eines Applets prüft der Browser, ob der Bytecode gewisse Sicherheitsbedingungen erfüllt. Erst wenn hier keine Bedenken vorliegen, wird das Applet gestartet. Dies ist auch der Grund, warum es immer etwas dauert, bis ein Applet auf einer HTML-Seite angezeigt wird (die eigentliche Ladezeit für den Appletcode spielt natürlich auch eine Rolle). Nun ist es aber keineswegs so, dass das Applet jetzt nach Herzenslust tun und lassen kann, was es will. Es unterliegt während der Ausführung strengen Einschränkungen, unter anderem ■ ■ ■ ■
darf es keine Dateien auf dem lokalen Dateisystem (also auf Ihrem Rechner) lesen oder beschreiben, darf es keine Internet-Verbindungen zu anderen Rechnern aufnehmen, außer zu dem Rechner, von dem es geladen worden ist (sein Server), darf es keine Systeminformationen feststellen, z. B. den Benutzernamen oder den Namen des Home-Verzeichnisses, wenn das Applet weitere Fenster aufmacht, erhält der Benutzer eine deutlich sichtbare Markierung, dass es sich um ein unsicheres Fenster handelt.
Es gibt noch eine Reihe weiterer Einschränkungen, die hier nicht weiter verfolgt werden sollen, aber Sie merken schon: Ein Applet darf eigentlich nicht viel machen, es ist von der Außenwelt (= Ihrem Rechner) weitgehend abgeschottet. Diesen Ansatz nennt man Sandbox-Prinzip. Manchmal ist es allerdings zweckmäßig, bestimmten Applets doch mehr Rechte zu erlauben. Hierzu gibt es verschiedene Wege: Viele Browser erlauben eine Veränderung der Restriktionen (z. B. die Verwendung von signierten Applets), allerdings werden die Details, auf die an dieser Stelle nicht näher eingegangen werden soll, bei jedem Browser anders gehandhabt. Eine andere Möglichkeit ist der Einsatz des Programms policytool des JDK. Mit diesem Programm können Sie in einer Datei java.policy (Unix: .java.policy) bestimmten Applets mehr Rechte einräumen. Dies sollten Sie aber ganz bewusst machen und nach Möglichkeit vermeiden. Denken Sie daran: Die Welt kann böse sein! Allerdings haben viele Browser ohnehin Probleme mit diesem Mechanismus und ohne Java-Plug-In klappt es nach unseren Erfahrungen mit der aktuellen Browsergeneration nicht.
1062
Inhalt
39 JNI, eine Schnittstelle zu C/C++ 29 30
Wie Sie wissen, werden Java-Programme vom Compiler in einen plattformunabhängigen Zwischencode – den Bytecode – übersetzt, der dann während der Ausführung durch die Java Virtual Machine in den jeweiligen nativen Maschinencode übersetzt und ausgeführt wird. Dies bringt das Wörtchen nativ ins Spiel. Damit sind ausführbare Programme gemeint, also letztlich Maschinencode für den jeweiligen Prozessor, z. B. die x86 IntelFamilie.
31 32
JNI (Java Native Interface) ermöglicht es einem Entwickler, Java-Programme zu schreiben, die mit solchem nativen Code interagieren können. Die einzige Bedingung für den Einsatz von JNI ist dabei, dass der native Code aus C/C++-Quelltext generiert worden ist. Andere Sprachen wie Cobol oder Fortran werden nicht unterstützt.
33
Auf den ersten Blick erscheint es eventuell etwas seltsam, ein portables Java-Programm mit nativem Code zu verknüpfen. Schließlich verliert man dadurch die allseits gepriesene Portabilität.
35
Dieser Einwand ist völlig korrekt und auch das wesentliche Gegenargument zum Einsatz von JNI. Aber leider ist die Wirklichkeit – vor allem in Wirtschaftsunternehmen mit Hunderten von bestehenden IT-Systemen – unbarmherzig und verlangt von neu zu entwickelnden Programmen, dass sie Funktionalitäten von vorhandener Software, die in der Regel sehr viel Geld gekostet hat, nutzen und nicht alles neu entwickelt wird.
36
34
37 38
Deshalb werden Sie als Java-Programmierer im Unternehmensbereich früher oder später in den sauren Apfel beißen müssen und sich mit dem Problem konfrontiert sehen, aus einem Java-Programm heraus auf bestehenden C/C++-Code zugreifen zu müssen, und dann über JNI sehr dankbar sein! Ein anderer Grund für den Rückgriff auf C/C++-Code könnte schlichtweg Geschwindigkeit sein. Der Wechsel während der Programmausführung von einem Java-Programm in nativen Code kostet zwar einige Zeit, lohnt sich aber, wenn dort aufwändige Berechnungen durchgeführt werden, die in einer Java-Implementierung um Größenordnungen langsamer wären.
39 40 41
JNI bietet einem Entwickler im Wesentlichen zwei Mechanismen an:
42
Der Aufruf von C-Funktionen aus einem Java-Programm heraus. Der C/C++-Code muss dabei als dynamische Bibliothek (shared library unter Unix, DLL unter Windows) bereitgestellt werden. Wenn bestehende
1063
Index
■
JNI, eine Schnittstelle zu C/C++
■
C/C++-Funktionen genutzt werden sollen, dann müssen entsprechende Wrapper-Funktionen1 geschrieben werden, die das Bindeglied zwischen Java und C/C++ bilden. Der Aufruf von Java-Methoden aus nativen Funktionen heraus, die wiederum aus Java heraus aufgerufen worden sind.
In den folgenden Abschnitten werden diese zwei Möglichkeiten vorgestellt, wobei der Schwerpunkt auf dem Aufruf von C-Funktionen aus Java heraus liegen soll. Es wird dabei vorausgesetzt, dass Sie die Grundlagen von C/C++ beherrschen, sodass Sie sich auf die Java-Aspekte konzentrieren können.
TIPP
Der Aufruf einer nativen Methode aus einem Java-Programm ist ca. 2-3 mal langsamer als ein Aufruf einer Java-Methode. Bevor Sie sich also auf JNI stürzen, sollten Sie immer auch bedenken, ob es nicht andere attraktive Möglichkeiten zur Kommunikation zwischen Java-Code und dem Rest der Welt gibt, z. B. TCP/IP-Sockets oder JDBC/ODBC für den Zugriff auf Datenbanken.
39.1 Aufruf von nativem Code – Einstieg Das Erstellen einer Java-Anwendung, die auf nativen Code zugreift, erfordert eine Reihe von Schritten, die anhand eines Beispiels verdeutlicht werden sollen. Schritt 1: Erstellen der gewünschten Java-Klasse inklusive einer Deklaration der native-Methoden Schritt 2: Kompilieren der Klasse mit javac Schritt 3: Erzeugen einer C/C++-Headerdatei mit javah Schritt 4: Erstellen des C/C++-Codes Schritt 5: Generierung einer dynamischen Bibliothek aus dem C/C++-Code Als Beispiel möge das klassische »Hallo Welt«-Programm dienen. Die Ausgabe »Hallo Welt« soll jedoch nicht durch das Java-Programm selbst erfolgen. Stattdessen wird diese ehrenvolle Pflicht an eine C-Funktion delegiert. Gehen wir nun die notwendigen Schritte durch. Schritt 1: Native Methode deklarieren Listing 39.1: HalloWeltBeispiel.java – Der Java-Code für HalloWelt 01 public class HalloWeltBeispiel { 02 public static void main(String[] args) { 03 HalloWelt obj = new HalloWelt(); 04 obj.printHallo(); 05 } 06 } 07 1
1064
Das Java-Programm ruft die Wrapper C-Funktion auf, welche wiederum dann auf die eigentlichen Funktionen/Klassen der DLL zugreift.
Aufruf von nativem Code – Einstieg
08 class HalloWelt { 09 // Laden der Bibliothek mit der nativen Implementierung 10 static { 11 System.loadLibrary("HalloWelt"); 12 } 13 14 // Deklaration der nativen Methode 15 public native void printHallo(); 16 }
29
Die interessanten Aspekte finden sich in der Klasse HalloWelt. Hier müssen Sie die native Methode, die vom Java-Programm aus aufgerufen werden soll, deklarieren (Zeile 15). Dies erfolgt ganz analog zur üblichen Methodendefinition, nur dass das Schlüsselwort native vorangestellt wird und anstelle des Methodenrumpfes ein Semikolon gesetzt wird.
30 31
Die zugehörige C/C++-Funktion werden Sie später in eine dynamische Bibliothek namens HalloWelt packen. In der Java-Klasse HalloWelt laden Sie diese Bibliothek mittels eines loadLibrary()-Aufrufs (Zeilen 10-12).
32
33
Schritt 2: Kompilieren des Java-Programms Hierbei gibt es nichts Besonderes zu beachten. Einfach den Compiler javac aufrufen.
34
Schritt 3: Erzeugen einer C/C++ – Headerdatei
35
Der nächste Schritt besteht in der Erzeugung einer Headerdatei. Hierfür stellt das JDK das Hilfsprogramm javah bereit, das als Parameter den Namen der Klasse erwartet, die eine native-Methode deklariert hat. In unserem Beispiel also:
36
javah –jni HalloWelt
37
Das Ergebnis dieses Aufrufs ist eine Datei HalloWelt.h mit folgendem Inhalt: Listing 39.2: Die von javah erzeugte Headerdatei 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17
38
/* DO NOT EDIT THIS FILE - it is machine generated */ #include /* Header for class HalloWelt */
39
#ifndef _Included_HalloWelt #define _Included_HalloWelt #ifdef __cplusplus extern "C" { #endif /* * Class: HalloWelt * Method: printHallo * Signature: ()V */ JNIEXPORT void JNICALL Java_HalloWelt_printHallo (JNIEnv *, jobject);
40 41 42
1065
JNI, eine Schnittstelle zu C/C++
18 19 20 21
#ifdef __cplusplus } #endif #endif
Man sieht nun, was javah für die native Methode printHallo() erstellt hat: eine neue Signatur in C-Syntax, wobei der ursprüngliche Name printHallo() durch die Voranstellung Java und den Namen der Klasse, welche die Methode deklariert hat, ergänzt wurde. Beachten Sie, dass die eigentlich parameterlose Methode hier plötzlich zwei Parameter besitzt, auf die später eingegangen werden soll. Sie werden benötigt, wenn Parameter verarbeitet werden sollen. Mit dem Wissen der Signatur kann man nun mit der Implementierung beginnen. Schritt 4: Erstellen des C/C++-Codes Für die C/C++-Implementierung muss eine besondere Headerdatei jni.h inkludiert werden. Sie und weitere plattformabhängige Headerdateien sind in jedem JDK enthalten (z. B. c:\…\JDK1.6.0\include und c:\…\JDK1.6.0\ include\win32 für Windows). Kopieren Sie die darin vorhandenen Headerdateien in das Include-Verzeichnis Ihres C/C++-Compilers bzw. passen Sie dessen Standard-Include-Pfade beim Aufruf um diese Verzeichnisse entsprechend an (bei den meisten Compilern die Option –I). Neben jni.h muss außerdem noch die Headerdatei, die javah erzeugt hat, inkludiert werden; in unserem Beispiel ist dies HalloWelt.h. Dann folgt die Definition der Funktion gemäß der oben gezeigten Signatur. Listing 39.3: Datei HalloWelt.c für printHallo() 01 02 03 04 05 06
#include #include "HalloWelt.h" #include
JNIEXPORT void JNICALL Java_HalloWelt_printHallo( JNIEnv *env, jobject myClass) { 07 printf("Hallo Welt!\n"); 08 }
Schritt 5: Erstellen einer dynamischen Bibliothek Das Erstellen einer dynamischen Bibliothek aus dem obigen C-Programm ist plattform- und compilerabhängig. Der eigentliche Name der Bibliothek ist unwichtig. In diesem Kapitel trägt sie den gleichen Namen wie die zugrunde liegende C-Datei (also HalloWelt), aber auch jeder andere Name ist möglich. Der Name muss allerdings mit dem Namen übereinstimmen, der im JavaProgramm für das Laden mit System.loadLibrary() verwendet wird.
1066
Übergabeparameter und Rückgabewerte
Unter Windows trägt eine dynamische Bibliothek die Endung dll, d.h., hier erzeugt man eine Datei HalloWelt.dll. Für den Microsoft Compiler (Version 6) lautet der Kommandozeilenaufruf unter Windows XP beispielsweise: cl -Ic:\programme\java\JDK1.6.0\include Ic:\programme\java\JDK1.6.0\include\win32 –LD HalloWelt.c –FeHalloWelt.dll
Falls Sie andere Compiler verwenden, müssen Sie diese Aufrufe entsprechend anpassen. Hinweise zum Aufruf und den anzugebenden Optionen finden Sie in der Dokumentation des Compilers. Unter Linux heißt eine dynamische Bibliothek shared library und der Name muss dem Format libName.so folgen, d.h., hier erzeugt man eine Datei libHalloWelt.so.
29 30 31
TIPP
Schritt 6: Ausführen
32
Damit sind Sie im Wesentlichen fertig. Es muss noch dafür gesorgt werden, dass die dynamische Bibliothek während der Ausführung von der Java Virtual Machine gefunden werden kann. Für Windows bedeutet dies, dass die entsprechende DLL im lokalen Verzeichnis liegen muss (wo das Java-Programm gestartet wird) oder in einem Verzeichnis, das in der PATH-Umgebungsvariablen definiert ist. Auf Unix-Systemen muss die Bibliothek in einem Verzeichnis liegen, das in der Umgebungsvariablen LD_LIBRARY_PATH definiert ist.
33 34 35
Eine alternative Möglichkeit ist die Angabe des Bibliothek-Pfades als Systemeigenschaft für die Java Virtual Machine mittels –Djava.library.path (z. B. auf den Wert ».« für das aktuelle Verzeichnis).
36
Sobald die Umgebungsvariablen richtig gesetzt sind, kann das Java-Programm ganz normal gestartet werden und die »Hallo Welt«-Meldung erscheint, z. B.
37
java –Djava.library.path=. HalloWeltBeispiel
38
39.2 Übergabeparameter und Rückgabewerte
39
Im vorherigen Beispiel wurde der einfachste Fall betrachtet, bei dem keine Parameter übergeben werden und auch kein Rückgabewert zurückgegeben wird.
40
In der Praxis ist dies natürlich eher selten und Sie werden in diesem Abschnitt die Mechanismen kennenlernen, mit denen man von einem C/C++-Programm aus auf übergebene Parameter zugreifen bzw. Rückgabewerte bereitstellen kann. Hierbei muss man zwei Fälle unterscheiden: primitive Datentypen wie int, float, boolean und Objekte wie String oder Felder, z. B. int[].
41 42
1067
JNI, eine Schnittstelle zu C/C++
39.2.1 Primitive Datentypen Primitive Parameter, die von der Java-Seite aus übergeben werden, erscheinen auf der C-Seite im Funktionskopf als ein entsprechender C-Datentyp, die in jni.h per typedef definiert worden sind: Tabelle 39.1: Das Mapping von primitiven JavaDatentypen auf C/C++-Datentypen
Java-Typ
C/C++ Äquivalent
Beschreibung
boolean
jboolean
8 Bit, unsigned; Werte ungleich 0 gelten als true
byte
jbyte
8 Bit, signed
char
jchar
16 Bit, unsigned
short
jshort
16 Bit, signed
int
jint
32 Bit, signed
jsize
32 Bit, signed
long
jlong
64 Bit, signed
float
jfloat
32 Bit, signed
double
jdouble
64 Bit, signed
Die Java-Typen erscheinen in der C/C++-Welt also einfach mit einem vorangesetzten j. Dabei ist zu beachten, dass z. B. ein int in C je nach Plattform 16 oder 32 Bit repräsentieren kann. Das JNI-Mapping garantiert jedoch den obigen Wert, d.h., ein jint hat immer 32 Bit, unabhängig von der Plattform. Der Zugriff auf primitive Parameter gestaltet sich dank des obigen Mappings, das von JNI durchgeführt wird, ganz einfach. Eine in Java deklarierte native Methode einer Klasse Demo public native boolean printHallo(int a, float b, boolean c);
könnte dann im C-Code folgendermaßen erstellt werden: JNIEXPORT void JNICALL Java_Demo_printHallo( JNIEnv *env, jobject myClass, jint a, jfloat b, jchar c) { jboolean status; int meinA = a; /* Beispiel für Zuweisung */ printf("Hallo Welt!\n"); printf("Wert von a=%d\n",a); printf("Wert von b=%f\n",b); printf("Wert von c=%c\n",c); status = 1; // 1 = TRUE, 0 = FALSE return status; }
Wie Sie sehen können, muss das C-Programm nichts weiter beachten und kann die übergebenen Parameter ganz normal für Ausgaben oder Zuwei1068
Übergabeparameter und Rückgabewerte
sungen einsetzen. Bei letzterem kann es u. U. sein, dass der Compiler gemäß den Regeln der Sprache C/C++ nach einem expliziten Cast verlangt, je nachdem, auf welcher Plattform man kompiliert, und somit das obige Mapping intern anders abgebildet wird. Auch das Anlegen von Rückgabewerten verläuft wie in einem normalen C/C++-Programm. Es muss lediglich eine Variable vom Typ jboolean, jint, usw. angelegt werden und mit return an das aufrufende Java-Programm zurückgegeben werden.
29
39.2.2 Referenzen auf Objekte
30
Während der Zugriff auf Parameter mit primitivem Datentyp unkompliziert ist, muss man beim Einsatz von Objekten einige wichtige Details beachten, damit das Programm nicht abstürzt oder Memory Leaks verursacht. Hierzu muss nun ein Blick hinter die Kulissen von JNI geworfen werden, damit die Zusammenhänge klarer werden.
31 32
Wie Sie bereits gesehen haben, hat jede native C/C++-Funktion standardmäßig zwei Parameter: ■ ■
33
einen Zeiger env vom Typ JNIEnv einen Parameter myClass vom Typ jobject
34
Der Zeiger env verweist auf eine interne Datenstruktur der Java Virtual Machine und repräsentiert das Bindeglied zwischen der Java- und der C/C++-Welt. env dient unter anderem dazu, auf spezielle Hilfsfunktionen von JNI zuzugreifen. Der Einsatz der in diesem und den nachfolgenden Abschnitten vorgestellten JNI-Funktionen sind für den Einsatz von C ausgelegt. Für C++ gibt es im Wesentlichen die folgenden Besonderheiten: – –
35
36 INFO
37
Der JNIEnv-Parameter env ist nicht notwendig. Der Zugriff auf die Funktion erfolgt direkt über den JNIEnv-Zeiger, z.B.
38
// C-Aufruf (*env)->dieFunktion(env, ...);
39
// C++-Aufruf env->dieFunktion(...);
–
Außerdem muss bei C++ für die erstellte native Funktion die C-Linkage angegeben werden – durch Einfassung in einen extern "C" { }-Block:
40
extern "C" { JNIEXPORT void JNICALL Java_HalloWelt2_printHallo( JNIEnv *env, jobject myClass, jint a, jfloat b, jchar c) { jboolean status; ... } }
41 42
1069
JNI, eine Schnittstelle zu C/C++
Der myClass-Parameter kann als eine Art this-Zeiger interpretiert werden. Er ist vom Typ jobject, der wiederum über ein typedef in jni.h definiert ist als ein Zeiger auf eine Struktur (im Falle von C) bzw. eine Klasse (im Falle von C++) namens _jobject. Der Parameter myClass zeigt für C-Funktionen auf die Klasse, welche die native Funktion aufgerufen hat, und für native statische Klassenmethoden auf die Klasse, zu der die aufgerufene Methode gehört. Wenn einer nativen Funktion beim Aufruf in Java Objekte, d. h. Referenzparameter, übergeben werden, dann erscheinen diese Parameter im nativen Funktionskopf als Parameter mit speziellen Datentypen. Ähnlich wie bei den primitiven Datentypen stellt JNI ein Mapping bereit, um Referenztypen aus der Java-Welt auf geeignete C/C++-Typen abzubilden: Abbildung 39.1: Das Mapping von Java-Referenztypen auf C/C++-Typen
jobject jclass jstring jarray jobjectArray jbooleanArray jbyteArray jcharArray jshortArray jintArray jlongArray jfloatArray jdoubleArray jthrowable
Jedes Java-Objekt ist in der C/C++-Welt vom generellen Typ jobject und dann je nach Art gegebenenfalls von einem speziellen Untertyp. Instanzen von selbst erstellten Klassen sind dabei immer vom Typ jobject. Damit sind Sie für den Umgang mit Objekten gerüstet. Zeichenketten Einer der wichtigsten Referenztypen sind Zeichenketten (Strings). Ein Methodenparameter auf der Javaseite vom Typ String findet sich in der Signatur der aufgerufenen nativen C/C++-Funktion als Parameter vom Typ jstring wieder. Strings werden dabei gemäß der C-Konvention als nullterminierte ('\0') Zeichenketten repräsentiert. Ein kleines Problem ergibt sich jedoch aus dem Umstand, dass Java standardmäßig Zeichen im UnicodeZeichensatz ablegt, während die C/C++-Welt normalerweise mit 7-BitASCII arbeitet. Außerdem besteht hinsichtlich der Speicherhandhabung eine Schwierigkeit: Ein übergebener String-Parameter vom Typ jstring ist eine Referenz auf ein Stringobjekt in der Java Virtual Machine und kann somit nicht direkt im C/C++-Code eingesetzt werden. 1070
Übergabeparameter und Rückgabewerte
Zur Lösung dieser Probleme bietet JNI eine Reihe von Funktionen an, die beim Umgang mit Strings aus der Java-Welt behilflich sind. Die wichtigsten für den Umgang mit dem UTF-8 Format sind in Tabelle 39.2 aufgeführt2. Funktion
Beschreibung
char *GetStringUTFChars(JNIEnv *env, jstring text, NULL);
Erzeugt aus text eine nullterminierte Zeichenkette im UTF-8-Format und liefert einen Zeiger darauf zurück.
void ReleaseStringUTFChars(JNIEnv *env, jstring text, jbyte c_msg);
Gibt den Speicherbereich wieder frei, der durch GetStringUTFChars() angelegt worden war.
void GetStringUTFRegion(jstring text, jsize start, jsize num, char *puffer);
Kopiert aus text ab Position start num Unicode-Zeichen in puffer im UTF-8-Format.
jsize GetStringUTFLength(JNIEnv *env, jstring text);
Liefert die Anzahl an Bytes bei UTF-8-Darstellung des Strings text (ohne '\0' Zeichen).
jstring NewStringUTF(JNIEnv *env, const char *text);
Kreiert aus der Zeichenkette text im UTF-8-Format einen neuen jstring.
Tabelle 39.2: Wichtige JNIStringfunktionen
30 31 32
33 34
Bevor auf einen Java-String mit den üblichen C-Funktionen zugegriffen werden kann, muss er als nullterminierte Zeichenkette in 7-Bit-ASCII vorliegen. Hierfür kann man GetStringUTFChars() verwenden. Wenn der Java-String nur Zeichen aus dem 7-Bit-ASCII Bereich enthält (also z. B. nur Buchstaben und Zahlen), dann ist die von GetStringUTFChars() generierte UTF-8-Repräsentation automatisch auch 7-Bit-ASCII kompatibel und man kann den String z. B. mit printf() ausgeben.
35
36 37
Wichtig beim Einsatz von GetStringUTFChars() ist der Aufruf der besonderen Funktion ReleaseStringUTFChars(), wenn die angelegte Kopie nicht mehr benötigt wird. Dadurch wird der allokierte Speicher wieder freigegeben, sodass kein Speicherloch (Memory Leak) entsteht.
38
39
01 JNIEXPORT void JNICALL Java_Demo_printHallo( 02 JNIEnv *env, jobject myClass, jstring j_text) { 03 const char *c_text; 04 c_text = (*env)->GetStringUTFChars(env, j_text, NULL); 05 06 if(c_text == NULL) 07 return; 08 09 printf("Nachricht: %s\n",c_text); 10 11 (*env)->ReleaseStringUTFChars(env,j_text, c_text); 12 } 2
29
40 41 42
Darüber hinaus gibt es noch eine Reihe von Funktionen für den Umgang mit Unicode. Die Funktionen heißen genauso, aber ohne den Zusatz UTF.
1071
JNI, eine Schnittstelle zu C/C++
Beachten Sie die Abfrage in Zeile 6, ob der C-String erfolgreich angelegt werden konnte. Falls dies fehlgeschlagen ist (Rückgabewert NULL), wird eine schwebende Exception ausgelöst, die im laufenden C/C++-Programm zwar keine Auswirkung hat, aber bei der Rückkehr in das aufrufende Java-Programm ihre Wirkung entfaltet und dort bei Bedarf abgefangen werden sollte. Im C-Programm sollte daher in der Regel sofort der Rücksprung nach Java erfolgen, wenn GetStringUTFChars() nicht erfolgreich war.
INFO
Bei den meisten JNI-Funktionen, die einen Zeiger zurückliefern, zeigt ein Rückgabewert von NULL an, dass eine schwebende Exception in der Java Virtual Machine ausgelöst worden ist. In diesem Fall sollte der C-Code sofort ins Java-Programm zurückspringen, da alle weiteren JNI-Funktionsaufrufe sowieso fehlschlagen würden. Arrays Ein Array als Methodenparameter auf der Javaseite wird in der Signatur der aufgerufenen nativen C/C++-Funktion als Parameter vom Basistyp jarray abgebildet. Je nachdem, um welche Art von Array es sich handelt, kommen verschiedene Untertypen zur Auswahl: ■
■ ■
Ein Array von primitiven Datentypen wie boolean, byte, int, usw. wird zu einem Parameter vom Typ jbooleanArray, jbyteArray, jintArray, usw. Ein Array von Objekten wird zu einem Parameter vom Typ jobjectArray. Ein mehrdimensionales Array wird zu einem Parameter vom Typ jobjectArray.
Ähnlich wie bei Strings, die man wegen ihrer Repräsentation als jstring nicht unmittelbar bearbeiten kann, ist es auch bei den Array-Typen notwendig, mittels JNI-Hilfsfunktionen auf die Daten zugreifen. Für Arrays mit primitiven Datentypen sind dies die in Tabelle 39.3 genannten3. Tabelle 39.3: JNI-Funktionen zum Zugriff auf primitive Arrays
Funktion
Beschreibung
jsize GetArrayLength(JNIEnv *env, jarray feld);
Liefert die Anzahl an Elementen im Array.
jXxx *GetXxxArrayRegion(JNIEnv *env, jXxxArray feld, int start, int num, jXxx *puffer);
Kopiert num Einträge des Arrays feld vom primitiven Datentyp xxx (int, float, etc) beginnend bei Index start in einen bereitgestellten puffer.
3
1072
Es gibt darüber hinaus noch einige weitere Spezialfunktionen, die hier nicht behandelt werden.
Übergabeparameter und Rückgabewerte
Funktion
Beschreibung
void SetXxxArrayRegion(JNIEnv *env, jXxxArray feld, int start, int num, jXxx *puffer);
Kopiert num Einträge aus einem bereitgestellten puffer ab Index start in das Array feld.
jXxx *GetXxxArrayElements(JNIEnv *env, jXxxArray feld, NULL);
Liefert einen Pointer auf einen neu allokierten Speicherblock, wo der Inhalt des Arrays vom primitiven Datentyp Xxx kopiert worden ist.
void ReleaseXxxArrayElements( JNIEnv *env, jXxxArray feld, jXxx *kopie, 0);
Deallokiert den Speicherbereich, der durch GetXxxArrayElements() angelegt wurde.
jXxxArray NewXxxArray(JNIEnv *env, jsize num);
Erzeugt ein neues Array vom primitiven Typ xxx mit num Einträgen.
Tabelle 39.3: JNI-Funktionen zum Zugriff auf primitive Arrays (Forts.)
29 30 31 32
33
Eine in Java definierte native Methode public native void print(int[] zahlen) zur Ausgabe eines Integer-Arrays könnte folgendermaßen in C realisiert werden:
34
Listing 39.4: ArrayParam.c – Beispiel für Zugriff auf primitive Arrays 01 #include 02 #include "ArrayParam.h" 03 04 JNIEXPORT void JNICALL Java_ArrayParam_print(JNIEnv *env, 05 jobject myClass, 06 jintArray zahlen) { 07 jsize anzahl; 08 jint *puffer; 09 int i; 10 11 12 anzahl = (*env)->GetArrayLength(env,zahlen); 13 printf("Anzahl der Zahlen: %d\n", anzahl); 14 15 // Variante A: eigenen Puffer bereitstellen 16 puffer = (jint *) malloc(anzahl * sizeof(jint)); 17 18 for(i = 0; i < anzahl; i++) 19 puffer[i] = 0; 20 21 if(puffer != NULL) { 22 // Elemente umkopieren 23 (*env)->GetIntArrayRegion(env, zahlen, 0, anzahl, puffer); 24 } 25 26 for(i = 0; i < anzahl; i++) 27 printf("%d ", puffer[i]);
35
36 37
38
39 40 41 42
1073
JNI, eine Schnittstelle zu C/C++
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 }
printf("\n\n"); // Speicher freigeben free(puffer); // Variante B: durch JNI auch Puffer bereitstellen lassen puffer = (*env)->GetIntArrayElements(env, zahlen, NULL); for(i = 0; i < anzahl; i++) printf("%d ", puffer[i]); printf("\n\n"); // Speicher freigeben (*env)->ReleaseIntArrayElements(env,zahlen, puffer, 0);
Zunächst wird in Zeile 12 mithilfe von GetArrayLength() die Anzahl an Elementen im Array ermittelt. Das Listing zeigt dann zwei Alternativen für die Ausgabe. In Variante A (Zeilen 15-32) legen Sie selbst einen Puffer an und verwenden die Methode GetIntArrayRegion(), um die Zahlen in den Puffer zu kopieren. In Alternative B (Zeile 34-43) überlassen Sie auch das Speicherallokieren JNI durch Einsatz von GetIntArrayElements(). Beachten Sie den Aufruf von ReleaseIntArrayElements(), den man nicht vergessen sollte, damit die erstellte Kopie wieder freigegeben wird. Wie Sie sehen können, ist der Einsatz von GetIntArrayElements() eigentlich einfacher, da man sich um das Speicherallokieren nicht selbst kümmern muss. Allerdings hat man dafür den Nachteil, dass immer das gesamte übergebene Array kopiert wird. Falls beispielsweise sehr große Arrays übergeben werden, aber nur bestimmte Bereiche daraus benötigt werden, ist der Weg über GetIntArrayRegion() vorzuziehen, da man hier die Kontrolle darüber hat, welcher Bereich kopiert werden soll. Bei übergebenen Arrays vom Typ jobjectArray, also bei Übergabe im JavaCode von mehrdimensionalen Arrays oder Arrays von Objekten, muss man etwas anders verfahren. Hierfür gibt es weitere JNI-Hilfsfunktionen, die in Tabelle 39.4 angegeben sind. Tabelle 39.4: JNI-Funktionen zum Zugriff auf Objekt-Arrays
1074
Funktion
Beschreibung
jobject GetObjectArrayElement( JNIEnv *env, jobjectArray feld, jsize index);
Liefert das Objekt aus dem Array feld an Position index.
void SetObjectArrayElement(JNIEnv *env, jobjectArray feld, jsize index, jobject obj);
Setzt im Array feld an Position index den Wert auf obj.
Übergabeparameter und Rückgabewerte
Funktion
Beschreibung
jXxxArray NewXxxArray(JNIEnv *env, jsize num);
Erzeugt ein neues Array vom primitiven Typ xxx mit num Einträgen.
jobjectArray NewObjectArray(JNIEnv *env, jsize num, jclass klasse, NULL);
Erzeugt ein neues Objekt-Array vom Typ klasse mit num Einträgen.
jclass FindClass(JNIEnv *env, char *name);
Liefert eine Referenz auf ein Objekt der Klasse name.
Tabelle 39.4: JNI-Funktionen zum Zugriff auf Objekt-Arrays (Forts.)
29 30
Wie Sie sehen können, gibt es im Gegensatz zu primitiven Arrays bei den Objektarrays keine Kopierfunktionen, sondern es ist lediglich möglich, einzelne Einträge zu lesen oder zu setzen. Dafür kann man aber ganze Arrays neu erstellen, z. B. mit NewObjectArray() für Objektarrays oder NewXxxArray() für primitive Arrays.
31
Für das Anlegen von Objektarrays mittels NewObjectArray() muss man eine Referenz auf den anzulegenden Klassentyp besitzen. Hierzu dient FindClass(), der man einen String mit dem gewünschten JNI Array-Klassendeskriptor übergibt. Ein solcher Deskriptor hat eine recht kryptische Form: ein [ gefolgt von einem Großbuchstaben für den primitiven Datentyp: [D ist ein double-Array, [F = float-Array, [I = int-Array, [Z = boolean-Array, [B = byte-Array, [C = char-Array.
33
32
34 35
Die in Tabelle 39.4 aufgeführten Funktionen zum Zugriff auf Objektarrays sehen recht harmlos aus, aber der Einsatz in der Praxis kann dann doch recht verzwickt sein und soll daher anhand einer konkreten Anwendung demonstriert werden. Es soll eine native Funktion erstellt werden, die ein zweidimensionales float-Array und einen Faktor erhält, alle Elemente des Arrays mit dem Faktor multipliziert und als neues Array zurückgibt.
36 37
Beispiel
38
Die Java-Signatur könnte folgendermaßen aussehen:
39
public native float[][] multipliziere(float faktor,float[][] feld);
Die Implementierung als C-Funktion muss mit dem Problem zurechtkommen, dass ein zweidimensionales Array sowohl in Java als auch in C/C++ ein Array von Arrays ist.
40
Listing 39.5: MultiArray.c – Beispiel für Zugriff auf primitive Arrays 01 02 03 04 05 06
#include #include #include #include
41
"MultiArray.h"
42
JNIEXPORT jobjectArray JNICALL Java_MultiArray_multipliziere ( JNIEnv *env, jobject myClass, jfloat faktor,
1075
JNI, eine Schnittstelle zu C/C++
07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
1076
jobjectArray zahlen) { jsize i_dim, j_dim; jsize i, j; jobject tmp; jobjectArray ergebnis; jfloatArray subArrayErgebnis; jfloatArray subArrayQuelle; jclass klasse; float wert; jfloat *werteQuelle; jfloat *werteErgebnis; // erst i_dim = tmp = j_dim =
die Dimensionen bestimmen (*env)->GetArrayLength(env,zahlen); (jarray)(*env)->GetObjectArrayElement(env, zahlen, 0); (*env)->GetArrayLength(env,tmp);
// Hilfspuffer anlegen werteQuelle = (jfloat *) malloc(j_dim + sizeof(jfloat)); werteErgebnis = (jfloat *) malloc(j_dim + sizeof(jfloat)); // das Ergebnisfeld anlegen und mit Werten belegen // Zunächst die i-Dimension anlegen klasse = (*env)->FindClass(env,"[F"); ergebnis = (*env)->NewObjectArray(env, i_dim, klasse, NULL); if(ergebnis == NULL) return NULL; for(i = 0; i < i_dim; i++) { // das Unterfeld (j-Dimension) erst anlegen... subArrayErgebnis = (*env)->NewFloatArray(env,j_dim); if(subArrayErgebnis== NULL) return NULL; // und dann zuweisen (*env)->SetObjectArrayElement(env, ergebnis, i, subArrayErgebnis); // das aktuelle Unterarray der Quelldaten nehmen subArrayQuelle = (jfloatArray) (*env)->GetObjectArrayElement(env, zahlen, i); // zum Zugriff brauchen wir einen Hilfspuffer (*env)->GetFloatArrayRegion(env, subArrayQuelle,0, j_dim, werteQuelle); for(j = 0; j < j_dim; j++) { // den Ergebniswert berechnen werteErgebnis[j] = faktor * werteQuelle[j];
Zugriff auf Java-Methoden
56 57 58 59 60 61 62 63 }
} // das Ergebnisarray zuweisen (*env)->SetFloatArrayRegion(env, subArrayErgebnis, 0, j_dim, werteErgebnis); }
29
return ergebnis;
Das übergebene zweidimensionale Array ist genau genommen ein Objektarray, bei dem jeder Eintrag ein Array vom Typ float ist. Gemäß dieser Erkenntnis wird in den Zeilen 19-21 die Größe des Arrays ermittelt. Für die erste Dimension erfolgt dies für das übergebene Array zahlen mittels GetArrayLength(). Für die zweite Dimension muss auf eines der Unterarrays zugegriffen und dessen Länge ermittelt werden. In Zeile 20 wird deshalb ein beliebiges Unterfeld (hier bei Index 0) gewählt und seine Länge mit GetArrayLength() bestimmt. In den Zeilen 29 und 30 wird nun das Ergebnisarray vorbereitet, indem die erste Dimension mithilfe von NewObjectArray() angelegt wird. Beachten Sie, wie FindClass() mit einem Deskriptor [F für floatArrays eingesetzt wird, da jeder Eintrag in ergebnis ein Unterarray vom Typ jfloat sein soll.
30 31 32
33 34
Die eigentliche Arbeit erfolgt in der for-Schleife ab Zeile 35. Die erste Dimension des übergebenen Arrays zahlen wird durchlaufen und für jedes Unterarray ein entsprechendes Unterarray im Ergebnisarray ergebnis angelegt (Zeilen 38-44). Das eigentliche Rechnen inklusive Zugriff auf die übergebenen Daten erfolgt mit den Hilfsarrays werteQuelle[] und werteErgebnis[] (beide vom Typ jfloat). In Zeile 47 wird mit GetObjectArrayElement() das jeweilige Unterarray ermittelt und mit der Funktion GetFloatArrayRegion() der komplette Inhalt in das Hilfsarray werteQuelle[] kopiert. Nach Durchführung der eigentlichen Berechnung geht das Spiel nun anders herum: Der Inhalt des Hilfsarrays werteErgebnis[] wird in das jfloat-Array mithilfe der Funktion SetFloatArrayRegion() (Zeile 59) kopiert.
35
36 37
38
39.3 Zugriff auf Java-Methoden
39
In den vorangegangenen Abschnitten wurde ein geradliniger Einsatz von JNI angenommen: das Java-Programm ruft eine native Methode auf, übergibt notwendige Daten als Parameter und erhält gegebenenfalls ein Ergebnis als Rückgabewert zurück. Es könnte aber auch Situationen geben, wo man aus der nativen Funktion heraus Java-Methoden aufrufen will.
40 41
Auch hierfür bietet das JNI eine entsprechende Unterstützung. Man kann sowohl Java-Methoden aufrufen als auch Klassen- und Instanzvariablen lesen oder setzen. Drei wesentliche Zutaten werden dabei benötigt:
42
1077
JNI, eine Schnittstelle zu C/C++
■
■ ■
eine Referenz auf die aufrufende Instanz Diese Referenz wird standardmäßig von der JVM an die aufgerufene native Funktion übergeben (als Parameter vom Typ jobject) und ist eine Art this-Zeiger. eine Referenz vom Typ jclass zur Beschreibung, um welche Art von Klasse es sich handelt eine Field-ID (für Variablen) bzw. eine Method-ID (bei Methoden) zur Identifikation der gewünschten Variablen/Methode
Die typische Vorgehensweise sieht dann folgendermaßen aus: Mithilfe der Funktion GetObjectClass() besorgt man sich vom übergebenen this-Zeiger eine Referenz, um zu wissen, welche Art von Klasse vorliegt: // jobject meinObj als Parameter der C-Funktion jclass klasse; klasse = (*env)->GetObjectClass(env, meinObj);
Dann wird die ID der gewünschten Variablen oder Methode mithilfe besonderer Field-Funktionen ermittelt (Tabelle 39.5). Tabelle 39.5: JNI-Funktionen zur Ermittlung von Variablen-/ Methoden-IDs
Funktion
Beschreibung
jfieldID GetFieldID(JNIEnv *env, jclass klasse, const char *name, const char *des);
Liefert die ID der Instanzvariablen name in der Klasse klasse mit dem Typ des.
jfieldID GetStaticFieldID(JNIEnv *env, jclass klasse, const char *name, const char *des);
Liefert die ID der Klassenvariablen name in der Klasse klasse mit dem Typ des.
jfieldID GetMethodID(JNIEnv *env, jclass klasse, const char *name, const char *des);
Liefert die ID der Methode name in der Klasse klasse mit der Signatur des.
jfieldID GetStaticMethodID(JNIEnv *env, jclass klasse, const char *name, const char *des);
Liefert die ID der statischen Methode name in der Klasse klasse mit der Signatur des.
Die verschiedenen GetFieldID()-Funktionen erwarten dabei neben dem Namen der Variablen oder Methode und einer Klassenreferenz vom Typ jclass noch einen besonderen Deskriptor-String, der für primitive Datentypen, wie schon im Abschnitt »Arrays« beschrieben, den Datentyp angibt, z. B. "I" für eine int-Variable, "F" für eine float-Variable usw. Bei Referenzvariablen ist die Syntax etwas komplizierter und beginnt mit einem L, gefolgt von dem vollen Namen der Klasse (also mit Paket) und einem Semikolon, wobei die üblichen Punkte im Klassennamen wie z. B. java.lang.String 1078
Zugriff auf Java-Methoden
durch / ersetzt sind. Für Methoden gibt es das Kürzel () sowie weitere Zeichen für Parameter und Rückgabewerte, z. B. "()V" für eine void-Methode ohne Parameter. An dieser Stelle wird auf eine genaue Angabe der Regeln verzichtet, da man hierbei sehr leicht Fehler machen kann. Glücklicherweise gibt es eine Alternative: das im JDK enthaltene Programm javap kann die gewünschte Information liefern, indem man ihm die jeweilige Class-Datei folgendermaßen übergibt:
29 30
javap –s –p meineKlasse
Der letzte Schritt besteht dann im Zugriff auf die Variable oder Methode mithilfe der ermittelten ID und einer der in Tabelle 39.6 aufgeführten Funktionen.
31 Tabelle 39.6: JNI-Funktionen zum Zugriff auf Java-Variablen und -Methoden
32
Funktion
Beschreibung
jXxx GetXxxField(JNIEnv *env, jobject obj, jfieldID id);
Liefert den Wert der Instanzvariablen vom primitiven Typ xxx mit der angegebenen id.
jobject GetObjectField(JNIEnv *, jobject obj, jfieldID id);
Liefert eine Referenz einer Instanzvariablen mit der angegebenen id.
34
void SetXxxField(JNIEnv *env, jobject obj, jfieldID id, jXxx wert);
Setzt die primitive Instanzvariable vom Typ xxx auf wert.
35
void SetObjectField(JNIEnv *env, jobject obj, jfieldID id, jobject wert);
Setzt die Instanzvariable mit ID id auf eine Referenz wert.
GetStatic...Field(); SetStatic...Field();
Analoge Funktionen wie oben für den Zugriff auf Klassenvariablen. Unterschied: Parameter obj ist vom Typ jclass.
void CallVoidMethod(JNIEnv *env, jobject obj, jmethodID id,...);
Ruft die Instanzmethode der ID id. Rückgabewert void.
jXxx CallXxxMethod(JNIEnv *env, jobject obj, jmethodID id, ...);
Ruft die Instanzmethode der ID id. Rückgabewert jXxx.
33
36 37
38
39 40 41 42
1079
JNI, eine Schnittstelle zu C/C++
Tabelle 39.6: JNI-Funktionen zum Zugriff auf Java-Variablen und -Methoden (Forts.)
Funktion
Beschreibung
jobject CallObjectMethod(JNIEnv* env, jobject obj, jmethodID id, ...);
ject.
CallStatic...Method();
Ruft die Instanzmethode der ID id. Rückgabewert job-
Analoge Funktionen zum Aufruf von static-Methoden. Unterschied: Parameter obj ist vom Typ jclass statt jobject.
Die Funktionen erhalten dabei immer eine Referenz vom Typ jobject auf die aufrufende Instanz oder bei Zugriff auf statische Variablen/Methoden die Referenz vom Typ jclass auf die entsprechende Klasse. Die folgende Java-Klasse hat Instanz- und Klassenvariablen sowie eine Java-Methode und eine native Methode zur Ausgabe dieser Variablen: Listing 39.6: JavaMethodBeispiel.java – Aufruf von Java-Methoden aus einer nativen Methode heraus public class JavaMethodBeispiel { static { System.loadLibrary("JavaMethod"); } // Klassenvariable static String verlag = "Pearson"; // Instanzvariablen int jahr; String name; // Methoden void printNameJahr() { System.out.println(" Java-Methode: Name = " + name); System.out.println(" Jahr = " + jahr); } // Hauptmethode public static void main(String[] args) { JavaMethodBeispiel obj = new JavaMethodBeispiel(); obj.jahr = 2003; obj.name = "Java Kompendium"; obj.printVariablen(); } // Deklaration der nativen Methode public native void printVariablen(); }
In der nativen C-Funktion printVariablen() wird nun Folgendes verwirklicht: Zuerst wird auf die Instanz- und Klassenvariablen der aufrufenden Java-Klasse zugegriffen und die Werte ausgegeben. Dann werden die Vari-
1080
Zugriff auf Java-Methoden
ablen auf neue Werte gesetzt und anschließend von C aus die Java-Methode printNameJahr() zur Kontrolle aufgerufen, ob die neuen Werte gesetzt sind. Listing 39.7: JavaMethod.c – Zugriff auf Java-Methoden und Java-Variablen von C aus 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
#include #include #include #include
"JavaMethodBeispiel.h"
29
JNIEXPORT void JNICALL Java_JavaMethodBeispiel_printVariablen ( JNIEnv *env, jobject meinObj) { jfieldID nameID, jahrID, verlagID; jmethodID methodID; jclass klasse; jstring j_name, j_verlag; jint jahr; const char *name; const char *verlag;
30 31 32
klasse = (*env)->GetObjectClass(env, meinObj);
33
// Field-IDs ermitteln nameID = (*env)->GetFieldID(env, klasse, "name", "Ljava/lang/String;"); jahrID = (*env)->GetFieldID(env, klasse, "jahr", "I"); verlagID = (*env)->GetStaticFieldID(env, klasse, "verlag","Ljava/lang/String;");
34 35
if(nameID != NULL && jahrID != NULL && verlagID != NULL) { // Zugriff auf String-Variable ( = Objekt) j_name = (jstring)(*env)->GetObjectField(env, meinObj,nameID); name = (*env)->GetStringUTFChars(env, j_name,NULL);
36 37
// Zugriff auf int-Variable jahr = (*env)->GetIntField(env, meinObj, jahrID);
38
// Zugriff auf static-String Variable j_verlag = (jstring) (*env) >GetStaticObjectField(env, Klasse, verlagID); verlag = (*env)->GetStringUTFChars(env, j_verlag, NULL);
39
if(name != NULL && verlag != NULL) { printf("C-Funktion : Name = %s\n",name); printf(" Jahr = %d\n", jahr); printf(" Verlag = %s\n", verlag); }
40 41
// Speicher freigeben (*env)->ReleaseStringUTFChars(env,j_name,name); (*env)->ReleaseStringUTFChars(env,j_verlag,verlag);
42
// neue Werte zuweisen (*env)->SetIntField(env, meinObj, jahrID, 2005);
1081
JNI, eine Schnittstelle zu C/C++
46 47 48 49 50 51 52 53 54
j_name = (*env)->NewStringUTF(env,"Java Profi"); if(j_name != NULL) { (*env)->SetObjectField(env, meinObj, nameID, j_name); } } // eine Java-Instanz-Methode aufrufen methodID = (*env)->GetMethodID(env, klasse, "printNameJahr", "()V");
55 56 if(methodID != NULL) { 57 (*env)->CallVoidMethod(env, meinObj, methodID); 58 } 59 }
Der Zugriff auf Variablen und Methode beginnt in Zeile 15 mit der Ermittlung der Klassenreferenz auf die aufrufende Klasse. Hierfür verwendet man GetObjectClass() und übergibt dabei dieser Funktion die Objektreferenz, welche die C-Methode als Übergabeparameter meinObj von der Java Virtual Machine erhalten hat. Der nächste Schritt besteht nun in den Zeilen 17-20 in der Ermittlung der notwendigen IDs. Der Zugriff auf die Variablen erfolgt dann wie oben erläutert mit den jeweiligen GetXxxField()-Methoden, z. B. in Zeile 24 auf ein Objekt vom Typ String. In den Zeilen 53-58 sehen Sie ein Beispiel für das völlige analoge Vorgehen zum Aufruf einer Methode. Man ermittelt zuerst die Method-ID und verwendet dann die geeignete Call-Methode; in diesem Fall CallVoidMethod(), da die aufzurufende Java-Methode printNameJahr() keinen Rückgabewert hat.
1082
Inhalt
40 Sicherheit 29 30
Sicherlich haben auch Sie auf Ihrem Computer zahllose Hilfsprogramme (Tools) installiert, vom Download-Manager über ein Entpacker-Tool bis hin zum 01090-Warner, um nur einige typische Beispiele anzuführen. Die meisten dieser Programme haben Sie aus dem Internet heruntergeladen oder von einer Heft-CD einer Computer-Zeitschrift kopiert. Nach wenigen Mausklicks waren die Programme installiert und konnten gestartet werden, um ihre nützlichen Dienste anzubieten.
31 32
Aber sind Sie sicher, dass diese Programme auch nur das machen, was sie vorgeben zu tun? Vielleicht haben Sie schon von Spyware gehört: Es handelt sich dabei um Programme, die neben einer offiziellen Funktion (z. B. das Abspielen von MP3-Dateien) auch im Geheimen Informationen von Ihrem Computer über eine Internet-Verbindung nach außen berichten.
33
Wie kann man sich davor schützen? Die Antwort ist schwierig und lautet letztlich »gar nicht«, obwohl es natürlich viele Maßnahmen gibt, um die Gefahr zu vermindern: keine unbekannte Software installieren, Virenscanner regelmäßig laufen lassen, eine Personal Firewall einsetzen, usw.
35
34
36
Kern des Problems bleibt jedoch der Umstand, dass ein Programm, sobald es lokal von Ihrer Festplatte gestartet wird, sehr umfassende Zugriffsmöglichkeiten besitzt und alles machen kann, was auch der Benutzer darf, der das Programm gestartet hat. Sie sollten also immer ein großes Maß an Misstrauen gegenüber neuen Programmen haben, die auf Ihrem Rechner installiert und gestartet werden sollen. Letztlich läuft es immer auf die gleiche Frage hinaus: vertraue ich der Quelle bzw. dem Anbieter der Software und glaube, dass die Software nur das macht, was sie soll?
37 38 39
Das gleiche Problem stellt sich natürlich auch beim Einsatz von JavaAnwendungen und Applets. Allerdings bieten Java und die Java Virtual Machine einige Sicherheitsmechanismen an, die das Risiko etwas eindämmen und die Sie im Folgenden kennenlernen sollen.
40 41
40.1 Das Laden von Klassen
1083
Index
42
Wenn ein Java-Programm gestartet wird, z. B. durch den Aufruf java DemoProg, dann wird die Java Virtual Machine folgende Schritte abarbeiten:
Sicherheit
1.
Zunächst wird mittels eines speziellen Mechanismus der Bytecode der zugehörigen Klasse DemoProg.class geladen. 2. Der Bytecode wird darauf hin geprüft, ob er der Java-Spezifikation entspricht. 3. Falls die Klasse DemoProg Instanzvariablen von einem anderen Klassentyp besitzt oder von einer Klasse abgeleitet ist, wird der Bytecode dieser Klasse ebenfalls geladen und geprüft. 4. Die main()-Methode wird ausgeführt. 5. Während der Codeausführung wird bei jedem Antreffen von Klassen, die bisher noch nicht geladen worden sind, der zugehörige Bytecode geladen, verifiziert und gegebenenfalls ausgeführt. Für das Laden von Class-Dateien ist der Classloader (Klassenlader) zuständig. Eigentlich muss man im Plural sprechen, da es mehrere gibt: ■
■
den Bootstrap-Classloader zum Laden der Standardklassen1 aus dem Archiv rt.jar; er ist fester Bestandteil der Java Virtual Machine und in C programmiert. Benutzerdefinierte Lader, die in Java programmiert sind. Sie sind von der Klasse java.lang.ClassLoader abgeleitet.
Von den benutzerdefinierten Classloadern existiert bereits standardmäßig eine ganze Reihe, u. a. der Erweiterungsklassenlader zum Laden von zusätzlichen Klassen der Java-Bibliothek aus dem Verzeichnis jre/lib/ext der JavaInstallation, sowie der Systemklassenlader zum Laden von Klassen von der lokalen Festplatte gemäß der CLASSPATH-Umgebungsvariablen. Darüber hinaus existieren ein Applet-, Servlet- und ein RMI-Classloader zum Laden über eine Netzwerkverbindung. Die grundsätzliche Unterscheidung in Bootstrap-Lader und benutzerdefinierte Lader ist darin begründet, dass ein Lader der zweiten Kategorie per se als nicht vertrauenswürdig gilt, da er in Java implementiert ist und keinen integralen Bestandteil der Java Virtual Machine bildet. Aus diesem Grund gilt nur Klassencode, der über den Bootstrap-Lader in die Virtual Machine gelangt ist, als sicher und wird keiner weiteren Prüfung unterzogen. Bei allen anderen Classloadern wird der geladene Code überprüft. Hierzu kommt der Bytecode-Verifier ins Spiel, der den gesamten Code vor der Ausführung nach Sicherheitskriterien durchleuchtet. Er prüft, ob die Regeln für den Zugriff auf private Daten und Methoden eingehalten werden, ob Variablen vor ihrer Verwendung initialisiert sind und ob der LaufzeitStack nicht überlaufen kann.
1
Dies umfasst alle Klassen, die in den Standardpaketen von Java vorhanden sind – wie java.lang, java.io, usw.
1084
Das Laden von Klassen
Die genannten Sicherheitskriterien werden eigentlich schon durch den JavaCompiler javac beim Kompilieren sichergestellt, aber ein übler Zeitgenosse könnte ja eine eigene Version von javac erstellt haben, die sich an die JavaSpezifikation nicht hält, und dadurch bösartige Programme erstellen. Eine andere Möglichkeit wäre die direkte Manipulation des Bytecodes in den Class-Dateien mit einem Hex-Editor.
TIPP
29
Klassen können also über verschiedene Wege, d. h. Classloader, in die Virtual Machine gelangen. Die Klasse bzw. genauer ihr Bytecode liegt dann in Form einer Instanz der Klasse java.lang.Class vor.
30
Die Einheit von Klasse und der Instanz des Classloaders, der sie geladen hat, nennt man Runtime-Package2 oder Namespace (Namensraum). Die Java Virtual Machine merkt sich für jede geladene Klasse, über welche Instanz eines Classloaders sie geladen worden ist. Dadurch kann sie die folgende Sicherheitsregel gewährleisten:
31 32
»Wenn bei der Codeausführung in einer bereits geladenen Klasse K1 eine noch nicht geladene Klasse K2 referenziert wird, dann darf K2 nur mit dem gleichen Classloader (genauer: die gleiche Instanz!) geladen werden wie K1. Davon ausgenommen sind nur die Standardklassen der Java API, die über den Bootstrap-Lader geladen werden.«
33 34
Der Effekt dieser Sicherheitsregel ist, dass eine Java-Klasse immer nur die Klassen sehen kann, die mit derselben Classloader-Instanz geladen werden. Dadurch kann man verhindern, dass eine Klasse heimlich durch eine andere Klasse mit dem gleichen Namen ersetzt wird und ablaufender Code nicht mehr die Originalversion der Klasse verwendet. Wenn innerhalb eines Namensraums eine Klasse K bereits geladen ist, kann eine gleichlautende Klasse nicht mehr in diesen Namensraum geladen werden, da sie aus Sicht der Virtual Machine bereits vorhanden ist. Es ist lediglich möglich, über eine neue Instanz des verwendeten Classloaders die Klasse K zu laden, aber dann gehört sie definitionsgemäß einem anderen Namensraum an. Klassen in verschiedenen Namensräumen können aber nicht miteinander interagieren, wenn es nicht explizit zugelassen wird, und die Gefahr eines Wolfs im Schafpelz ist gebannt.
35
36 37
38
39
Es ist auch möglich, einen eigenen Classloader zu definieren, der als Ersatz bzw. Ergänzung zu den normalen Classloadern fungiert. Als Ausgangspunkt hierfür dient die Klasse java.lang.ClassLoader.
40 41 42
2
Daraus folgt, dass zwei Klassen mit identischem, qualifiziertem Namen (Paket plus Klassenname) in verschiedenen Runtime-Packages liegen, wenn sie über verschiedene Instanzen eines Klassenladers (auch wenn es der gleiche ist) geladen worden sind.
1085
Sicherheit
40.2 Der Sicherheitsmanager (Security-Manager) Wenn das Laden einer Klasse und alle damit verbundenen Prüfungen ohne Beanstandungen gemeistert wurden, wird der geladene Code endlich ausgeführt. Allerdings gibt es noch jemanden, der einen Blick darauf hält, was der gerade ablaufende Code anstellen will: der Sicherheitsmanager. Jedes von der Java Virtual Machine ausgeführte Programm besitzt entweder gar keine oder genau eine Instanz einer besonderen Klasse java.lang.SecurityManager, auf gut deutsch also einen Sicherheitsmanager. Sobald ein Java-Programm eine Instanz von SecurityManager erzeugt hat, kann diese nicht mehr verändert oder gar entfernt werden. Der Sicherheitsmanager ist dafür verantwortlich, während der Abarbeitung eines Programms festzustellen, ob das Programm für eine bestimmte Aktion eine ausreichende Berechtigung hat. Falls nicht, löst der Sicherheitsmanager eine Exception vom Typ AccessControlException aus. Dies setzt allerdings voraus, dass sensitive Codebereiche entsprechend abgesichert sind – nach dem Muster SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkRead(name); }
In den Java-Bibliotheken ist der obige Test an vielen Stellen vorhanden, beispielsweise wenn es um Festplatten- und Netzwerkzugriffe geht. Standardmäßig besitzt eine normale Java-Anwendung keinen Sicherheitsmanager und hat daher alle Rechte und kann tun und lassen, was sie will. Für Applets hingegen wird sofort beim Start eine von SecurityManager abgeleitete Klasse installiert, und dadurch ist klar, warum Applets viele Dinge nicht dürfen. Ein Applet darf Folgendes standardmäßig nicht tun: INFO
– – – – –
Zugriffe (schreibend, lesend, ausführend) auf die lokale Festplatte Zugriffe (lesend, schreibend) auf Umgebungsvariablen Netzwerkverbindungen (außer zum Ursprungshost) aufbauen die Java Virtual Machine beenden (z. B. System.exit()) einen eigenen Sicherheitsmanager installieren
Das Ermitteln und Setzen eines Sicherheitsmanagers erfolgt über die Klasse System und ihre Methoden getSecurityManager() und setSecurityManager(). Wenn man einer Java-Anwendung nicht völlig freie Hand geben will, was sie auf dem Rechner so treiben darf, muss zu Beginn eines Programms ein SecurityManager installiert werden:
1086
Der Sicherheitsmanager (Security-Manager)
public static void main(String[] args) { if(System.getSecurityManager() == null) { SecurityManager sm = new SecurityManager(); System.setSecurityManager(sm); }
29
// hier Programmcode }
Dies hilft natürlich nicht weiter, wenn man als Anwender Java-Programme ausführen will, die von irgendwo her bezogen worden sind, und man nicht genau weiß, ob man dem Programm über den Weg trauen darf. In diesen Fällen kann man dafür sorgen, dass die Java Virtual Machine einen Sicherheitsmanager setzt, indem man beim Aufruf die Option java.security.manager mitgibt, z. B. für die Ausführung des Programms Fremder:
30 31 32
java -Djava.security.manager Fremder
Wenn sichergestellt ist, dass ein Sicherheitsmanager aktiv ist, dann fehlt nur noch die Möglichkeit festzulegen, was der auszuführende Code im Einzelnen darf und was nicht. Dies lässt sich mit einer Sicherheitsrichtlinie (security policy) in Form einer besonderen Datei definieren. Sie trägt üblicherweise die Endung .policy und muss beim Aufruf als Option mitgegeben werden, z.B.
33 34
java –Djava.security.policy=rechte.policy –Djava.security.manager Fremder
35
Für die Policy-Datei gibt es recht umfangreiche Möglichkeiten zur Festlegung, was erlaubt sein soll und was nicht. Ein Eintrag besteht aus dem Schlüsselwort grant und einer Definition der erlaubten Zugriffsmöglichkeiten, z.B.
36 37
grant { permission java.net.SocketPermission "localhost:1024-", "listen"; permission java.util.PropertyPermission "java.version", "read"; };
38
In diesem Beispiel wird das Lauschen auf lokalen Sockets ab Portnummer 1024 erlaubt sowie das Lesen der Umgebungsvariablen java.version mithilfe der Property-Klasse.
39
Neben einer manuellen Bearbeitung einer Policy-Datei gibt es die wesentlich bequemere Möglichkeit, ein im JDK enthaltenes Hilfsprogramm einzusetzen: policytool, das von der Kommandozeile gestartet werden muss. Standardmäßig versucht das Programm, die Datei java.policy zu öffnen, die im Home-Verzeichnis des Benutzers gesucht wird3. Man kann das Programm aber auch für jede beliebige Policy-Datei verwenden.
40 41 42
3
Bei Windows XP beispielsweise im entsprechenden Benutzerverzeichnis in C:\Dokumente und Einstellungen.
1087
Sicherheit
Abbildung 40.1: Das policytool
Über den Schalter RICHTLINIENEINTRAG Berechtigungen festgelegt werden.
HINZUFÜGEN
können beliebig viele
Abbildung 40.2: Berechtigung für Dateizugriffe
In Abbildung 40.2 sehen Sie ein Beispiel, wie für den Dateizugriff festgelegt wird, dass auf alle Dateien sowohl lesend als auch schreibend oder löschend zugegriffen werden darf und ausführbare Dateien als Programm gestartet werden dürfen. Die Zugriffsrechte sind in Gruppen eingeteilt; die wichtigsten sind in Tabelle 40.1 aufgeführt4. Für jede Berechtigung kann man dabei das Ziel definieren – bei FilePermission z. B. den/die Dateiname(n) sowie die erlaubten Aktionen. Tabelle 40.1: Wichtige über die Policy-Datei festlegbare Berechtigungen
Berechtigung
Beschreibung
Ziel (Target)
Aktion
AllPermission
Voller Zugriff auf alle Ressourcen
-
-
AWTPermission
Zugriff auf das Window-System, z. B. auf accessClipboard, die Event-Queue oder das Clipboard accessEventQueue
-
FilePermission
Zugriff auf Dateien und Verzeichnisse
read, write, delete, execute
Datei- oder Verzeichnis
für gesamtes Dateisystem 4
1088
Eine umfassende Erläuterung bietet die J2SE-Dokumentation in der Datei docs/guide/security/permissions.html.
Der Sicherheitsmanager (Security-Manager)
Berechtigung
Beschreibung
Ziel (Target)
Aktion
PropertyPermission
Zugriff auf die Systemeigenschaften (Umgebungsvariablen)
Name der Systemeigenschaft oder *
read, write
SocketPermission
Durchführen von Socket-Verbindungen (TCP/IP, UDP)
IP-Adresse und Portnum- accept, mer-Bereich connect, listen, resolve
RunTime
Laufzeit-Rechte wie Instanziieren von ClassLoader und SecurityManagerObjekten oder das Laden von Bibliotheken.
StopThread, loadLibrary, queuePrintJob, exitVM u.a.
Tabelle 40.1: Wichtige über die Policy-Datei festlegbare Berechtigungen (Forts.)
29
-
30 31
Eine besonders gefährliche Option bei der Erstellung einer Sicherheitsrichtlinie ist die Berechtigung AllPermission, die alles zulässt und somit den Sicherheitsmanager eigentlich überflüssig macht. Sie sollte – wenn überhaupt – nur für Testzwecke eingesetzt werden und niemals in einer Produktionsumgebung eingestellt werden.
32
33
Neben den Berechtigungen lässt sich in einer Sicherheitsrichtlinie auch definieren, für welchen Code die Berechtigung gelten soll. Die Herkunft von Code wird als Codebase bezeichnet und ist ein Pfadname (z. B. file:/home/ meinCode/) oder ein URL (z. B. http:://www.website.de/public/). Wenn in der Policy-Datei keine Codebase explizit genannt wird, dann gilt Codebase=All, d.h., die Berechtigung betrifft jeden Code, egal von wo er geladen worden ist.
34 35
36
Eine weitere Option ist die Angabe eines Schlüsselspeichers (keystore). Es handelt sich dabei um eine einfache Datenbank, in der kryptografische Schlüssel und Zertifikate verwaltet werden können. Schlüsselspeicher werden im Abschnitt »40.3« näher erläutert.
37
Wenn eine Sicherheitsrichtlinie mit dem policytool wunschgemäß eingerichtet ist, dann muss die Datei nur noch in einem Verzeichnis abgespeichert werden, das zum CLASSPATH gehört, damit die SecurityManager-Instanz die Datei zur Laufzeit finden kann.
38
39
Zu einem beliebigen Zeitpunkt gibt es dabei häufig mehrere Policy-Dateien: ■
Im Verzeichnis jdk1.6.0\jre\lib\security der Java-Installation liegt die Standard-Policydatei java.policy. Sie definiert die sehr restriktiven Standardberechtigungen5 bei Einsatz eines Sicherheitsmanagers und wird zuerst geladen und ausgewertet.
40 41 42
5
Im Wesentlichen wird Klassen aus dem CLASSPATH nur erlaubt, auf Sockets zu lauschen sowie einige grundlegende Umgebungsvariablen zu lesen. Allerdings gilt dies nicht für Klassen, die in /lib/ext abgelegt werden. Sie haben volle Rechte.
1089
Sicherheit
■ ■
INFO
Wenn es im Home-Verzeichnis des Benutzers6 eine Datei .java.policy7 gibt, dann wird diese ausgewertet. Wenn über die Kommandozeile über –Djava.policy=xxx eine Datei xxx angegeben worden ist, dann wird diese zuletzt ausgewertet.
Wenn Sie auf einem Rechner arbeiten, wo Sie keine Administratorrechte besitzen, könnte es sein, dass Sie zwar munter Policy-Dateien erzeugen und Ihren Programmen mitgeben, diese aber ignoriert werden. Dann liegt dies daran, dass der Systemadministrator dies für Sie als normalen Benutzer gesperrt hat durch Auskommentieren der Zeile policy.allowSystemProperty=true in der Datei \jre\lib\security\java.security8. Das Sicherheitskonzept von Java, mithilfe von Sicherheitsrichtlinien die Rechte eines Programms feinkörnig festzulegen, ist zwar im Prinzip sehr modern und brauchbar, hat aber den Nachteil, dass es rein programmorientiert ist und nicht den Benutzer des Programms berücksichtigt. In der Praxis tritt jedoch häufig der Fall auf, dass man das gleiche Programm von verschiedenen Benutzern mit unterschiedlichen Rechten ausführen lassen will. Hierzu wird eine rollenbasierte Rechtevergabe notwendig, die es anfangs in Java nicht gab, aber seit der Version JDK 1.4 in Form der Komponente JAAS (Java Authentication and Authorization Service) existiert. Leider würde diese interessante Zusatzbibliothek den Rahmen dieses Buches sprengen, sodass hier nicht weiter darauf eingegangen werden soll.
40.3 Signierter Code Das zu Beginn dieses Kapitels angeschnittene Problem, dass man als Benutzer nicht weiß, ob der Hersteller/Anbieter xyz vertrauenswürdig ist, hat noch eine subtilere Variante: Selbst wenn man einem Software-Hersteller vertraut, wie kann man sicher sein, dass das Programm auf der Festplatte auch wirklich von diesem Hersteller stammt bzw. nicht auf dem Weg vom Hersteller zu Ihrer Festplatte modifiziert worden ist? Eine Antwort auf dieses Problem liefern digitale Unterschriften.
6
7 8
1090
Auf Unix-System im Verzeichnis $HOME, auf Windows 98 Rechnern in c:\windows, auf Windows 2000/XP unter c:\Dokumente und Einstellungen\Benutzername, auf Windows Vista unter c:\Users\Benutzername. Man beachte den Punkt am Anfang des Dateinamens! Diese Datei dient für globale Einstellungen, die alle Klassen aus dem Paket java.security betreffen. Dieses Paket enthält u. a. kryptografische Algorithmen und Zufallszahlengeneratoren.
Signierter Code
Abbildung 40.3: Prinzip der digitalen Unterschrift
29 30
Bei dieser genialen Idee für die Kommunikation zwischen einem Sender und einem Empfänger passiert Folgendes. Zu einer gegebenen Nachricht wird nach einem speziellen Verfahren eine relativ kurze Bitfolge9 berechnet, der sogenannte Message Digest. Man kann ihn sich als eine Superkomprimierung der ursprünglichen Nachricht vorstellen. Dieser Message Digest wird nun vom Sender mit seinem privaten Schlüssel verschlüsselt und zusammen mit der Nachricht an den Empfänger weitergereicht. Der Empfänger führt als Erstes ebenfalls die Berechnung des Message Digests durch. Dann decodiert er den verschlüsselten Message Digest, den er vom Sender erhalten hat. Hierzu hat er vom Sender dessen öffentlichen Schlüssel10 erhalten. Wenn der selbst berechnete Digest mit dem Digest vom Sender übereinstimmt, kann der Empfänger sicher sein, dass die erhaltene Nachricht nicht modifiziert worden ist und mit der Nachricht übereinstimmt, die der Sender abgeschickt hat.
31 32
33 34 35
Das Funktionieren dieses Prinzips hängt von zwei mathematischen Annahmen ab: ■
■
36
Zwei verschiedene Nachrichten (und sei auch nur ein einziges Bit verschieden und sonst alles identisch) führen zu verschiedenen Message Digests. Es ist mit realistischem Zeitaufwand nicht möglich, den privaten Schlüssel zu knacken bzw. aus dem öffentlichen Schlüssel den privaten zu berechnen.
37
38
Zum ersten Punkt kann man feststellen, dass ein Message Digest einer festen Länge (beispielsweise 160 Bit) maximal 2160 verschiedene Werte repräsentieren kann und es somit theoretisch möglich ist, dass zwei verschiedene Nachrichten doch den gleichen Message Digest haben. Allerdings ist die Wahrscheinlichkeit extrem gering und für alle praktischen Anwendungen vernachlässigbar. Die zweite Annahme gilt zum jetzigen Stand der Kryptografie ebenfalls als erfüllt, wobei es bisher noch keinen mathematisch unwiderlegbaren Beweis dafür gibt.
39
40 41
Wenn Sie in der obigen Schilderung des Nachrichtenaustauschs nun Sender durch Software-Hersteller, Empfänger durch Software-Käufer/Nutzer und
42
9 Die Länge hängt vom Algorithmus ab, beispielsweise 160 Bit bei SHA. 10 Öffentlicher und privater Schlüssel bilden ein zusammengehöriges Paar.
1091
Sicherheit
Nachricht durch Programm/JAR-Archive o. ä. ersetzen, haben Sie das Prinzip von signiertem Code vorliegen. In der Praxis hat sich signierter Code bisher nicht durchgesetzt und bleibt eine eher exotische Ausnahme. Ein wesentlicher Grund ist der Umstand, dass man für die Handhabung von signiertem Code eine Infrastruktur benötigt, nämlich Hilfsmittel für das Berechnen des Message Digest sowie das Decodieren mit einem öffentlichen Schlüssel. Darüber hinaus wird auch eine Zertifikatsverwaltung benötigt. Zertifikate
Ein digitales Zertifikat ist vereinfacht ausgedrückt eine kleine Datei, die den Namen einer Person enthält, zusammen mit ihrem öffentlichen Schlüssel. Diese Datei wurde von einer vertrauenswürdigen Firma11 digital signiert, d.h., es wurde ein Message Digest berechnet und von dieser Vertrauensstelle mit ihrem privaten Schlüssel codiert. Dieser verschlüsselte Message Digest gehört ebenfalls zum Zertifikat. Ein Zertifikat soll das Problem lösen, dass man als Empfänger einer signierten Nachricht ja nicht wirklich wissen kann, ob auch der öffentliche Schlüssel des Absenders, den man zur Kontrolle der Echtheit benötigt, wirklich vom Absender stammt! In der Praxis wird daher nicht einfach der öffentliche Schlüssel versendet, sondern das entsprechende Zertifikat. Wenn der Empfänger den Unterzeichner des Zertifikats kennt und vertraut, dann akzeptiert er den darin enthaltenen Schlüssel als echt Bei näherem Hinsehen wird dadurch das Problem natürlich nur verlagert: wie kann der Empfänger sicher sein, dass der öffentliche Schlüssel des vertrauenswürdigen Unterzeichners auch wirklich der echte ist? Er kann sich darauf verlassen, wenn er auch dessen öffentlichen Schlüssel in Form eines Zertifikats besitzt, das wiederum von einer anderen, noch vertrauenswürdigeren Stelle unterzeichnet worden ist. Dieses Spielchen lässt sich beliebig weitertreiben, bis man zu einem Zertifikat kommt, das nicht mehr von jemand anderem unterzeichnet ist, sondern das die im Zertifikat beschriebene Person/Firma selbst unterschrieben hat. Dies ist ein sogenanntes Root-Zertifikat, und dem muss man einfach trauen! Schauen wir uns nun ein Beispiel an, wie signierter Java-Code erzeugt und eingesetzt werden kann. Hierbei müssen zwei Seiten betrachtet werden. Auf der einen Seite steht der Software-Entwickler, der ein Programm erstellt hat und den Code signiert, auf der anderen Seite der Kunde, der den Code auf seinem Rechner ausführen will.
40.3.1 Schritte des Software-Entwicklers 1. 2.
Das Programm erstellen, kompilieren und als JAR-Archiv verpacken. Falls noch nicht vorhanden: ein Schlüsselpaar (öffentlicher und privater Schlüssel) erzeugen.
11 Beispielsweise die amerikanische Sicherheitsfirma Verisign oder die Deutsche Post.
1092
Signierter Code
3. Das JAR-Archiv signieren. 4. Signiertes JAR-Archiv und gegebenenfalls digitales Zertifikat (mit dem öffentlichen Schlüssel) an Kunden übergeben. Angenommen der Entwickler hat das folgende bahnbrechende Programm erstellt und kompiliert:
29
// Datei Signiert.java public class Signiert { public static void main(String[] args) { System.out.println(); System.out.println(" Vielen Dank, dass Sie uns vertrauen!"); System.out.println(" Ihr Account: " + System.getProperty("user.name")); } }
30 31 32
Dieses Programm greift auf die Umgebungsvariable user.name zu. Wenn ein Anwender das Programm unter Verwendung eines Sicherheitsmanagers (also mit der Option –Djava.security.manager) ausführen will, wird die Virtual Machine die Abarbeitung abbrechen, wenn sie auf den Aufruf getProperty() stößt.
33 34
Nach dem Kompilieren muss die zugehörige Class-Datei in ein JAR-Archiv verpackt werden:
35
jar cvfe Signiert.jar Signiert Signiert.class
Beachten Sie, dass obige Aufrufsyntax erst ab JDK 1.6 möglich ist. Wenn Sie mit einem älteren JDK arbeiten, müssen Sie zuerst von Hand eine Manifestdatei erstellen und diese mit der Option m hinzufügen (statt mit der Option e den Namen der Hauptklasse anzugeben). Mehr Informationen zu jar finden Sie im Anhang.
36 TIPP
37
38
Nun können Sie an das Signieren gehen. Dazu benötigen Sie ein Schlüsselpaar. Falls der Entwickler noch keines hat, kann er hierzu das im JDK mitgelieferte Programm keytool einsetzen:
39
keytool –genkeypair –alias carpelibrum –validity 1000
Dieser Aufruf erzeugt ein Schlüsselpaar, das unter dem Alias »carpelibrum« in der besonderen Schlüsseldatenbank (Datei .keystore) im Home-Verzeichnis des Benutzers12 abgelegt wird. Die Gültigkeitsdauer (validity) der erzeugten Schlüssel beträgt beim obigen Aufruf 1000 Tage. keytool erstellt neben den Schlüssel auch gleich ein Zertifikat und fragt daher noch zusätzliche Informationen wie Name der Organisation, Stadt und Land ab. Außerdem muss man ein Passwort für den Zugriff auf den Schlüsselspeicher
40 41 42
12 Unter Windows je nach Betriebssystem und Benutzerverwaltung: c:\Windows, c:\Windows\Profiles\ Benutzername, c:\Winnt\Profiles\Benutzername oder c:\Dokumente und Einstellungen\Benutzername bzw. c:\Users\Benutzername.
1093
Sicherheit
festlegen (falls es noch keinen gibt) sowie ein Passwort für den Zugriff auf das Schlüsselpaar mit dem gewünschten Alias. Signieren
Wenn das Schlüsselpaar vorliegt, kann das JAR-Archiv damit signiert werden, d.h., es wird ein Message Digest berechnet und mit dem privaten Schlüssel verschlüsselt. Diese Information wird dann dem JAR-Archiv hinzugefügt13. Zur Durchführung der Signierung dient das Hilfsprogramm jarsigner: jarsigner Signiert.jar carpelibrum
Es erwartet das zu signierende Archiv und den Alias des zu verwendenden Schlüssels. (Direkt nach dem Start werden Sie aufgefordert, das Passwort für den Zugriff auf den Schlüsselspeicher anzugeben.) Damit ist der Entwickler fertig und kann das signierte Archiv an den Kunden weitergeben. Außerdem benötigt der Kunde das Zertifikat mit dem öffentlichen Schlüssel. Dies kann der Entwickler aus dem Schlüsselspeicher exportieren durch den Aufruf keytool –exportcert –alias carpelibrum –file carpelibrum.cer
Mit der Option –printcert können Sie das Zertifikat ansehen: TIPP
keytool –printcert –file carpelibrum.cer
40.3.2 Schritte des Kunden Auf Kundenseite muss nun die Voraussetzung geschaffen werden, damit der signierte Code ausgeführt werden darf. Dies umfasst die folgenden Schritte: 1. 2.
Das Zertifikat des Software-Entwicklers – falls noch nicht bekannt – muss in den eigenen Schlüsselspeicher aufgenommen werden. Eine Sicherheitsrichtlinie muss erstellt bzw. so modifiziert werden, dass der signierte Code ausgeführt werden darf.
Der erste Schritt erfolgt mithilfe von keytool und ist schnell erledigt: keytool –importcert –alias carpelibrum –file carpelibrum.cer
Der Kunde kann dabei natürlich den Alias frei wählen. Für das Erstellen bzw. Anpassen der Sicherheitsrichtlinie kann man wieder das policytool verwenden, beispielsweise um die benutzerspezifische Policy-Datei .java.policy zu erstellen bzw. anzupassen. Zunächst muss der Schlüsselspeicher mit dem Entwickler-Zertifikat angegeben werden. Hierzu kann man über KEYSTORE/BEARBEITEN die Datei .keystore mit dem Schlüsselspeicher als URL angeben14. Wenn diese Datei 13 Aus diesem Grund kann man auch nur JAR-Archive signieren und nicht eine einzelne Class-Datei: wo sollte der verschlüsselte Message Digest sonst abgelegt werden? 14 Unter Windows je nach Betriebssystem und Benutzerverwaltung: c:\Windows, c:\Windows\Profiles\Benutzername, c:\Winnt\Profiles\Benutzername, c:\Dokumente und Einstellungen\Benutzername oder c:\Users\Benutzername.
1094
Signierter Code
beispielsweise unter Windows XP im Verzeichnis c:\Dokumente und Einstellungen\Administrator liegt, dann lautet die URL-Form file:/c:/Dokumente und Einstellungen/Administrator/.keystore. Als Keystore-Typ ist JKS einzutragen. Abbildung 40.4: Angabe der .keystore-Datei (Unter Vista für Benutzer Jerry)
29 30 31 32
33 34
Nun muss noch eine entsprechende Berechtigung eingetragen werden. Dies erfolgt über den Schalter RICHTLINIENEINTRAG HINZUFÜGEN. Zunächst muss dabei festgelegt werden, wer den Code signiert haben soll, damit die noch festzulegende Berechtigung gewährt werden kann.
35 Abbildung 40.5: Angabe des Aliasnamens des Code-Signierers
36 37
38
39
40 41 42
1095
Sicherheit
Hierzu gibt man in der SIGNIERT VON-Zeile den gewünschten Alias ein, in diesem Beispiel also carpelibrum, und erteilt dann die gewünschten Berechtigungen über den Schalter BERECHTIGUNG HINZUFÜGEN. Das Beispiel Signiert.jar versucht, die Umgebungsvariable user.name abzufragen. Daher erteilen wir weiterhin großzügig die Erlaubnis, auf alle Systemeigenschaften lesend zuzugreifen (Abbildung 40.6). Abbildung 40.6: Erlaubnis für Zugriff auf SystemProperties
Nach dem Speichern als Datei .java.policy im Home-Verzeichnis des Benutzers kann dann das Ausführen des Codes ganz normal erfolgen, obwohl der Anwender das Programm mit einem Sicherheitsmanager aufruft: java –Djava.security.manager –jar Signiert.jar
1096
Inhalt
41 Annotationen 29 30
Unter Annotationen (»Anmerkungen«) versteht man in Java besondere Zusatzinformationen im Quelltext. Man spricht daher auch manchmal von Java Metadaten. Mit solchen Zusätzen kann der Programmierer dem Compiler bzw. anderen Tools, die den Quelltext verarbeiten sollen, genauer mitteilen, was damit zu tun ist bzw. was der Programmierer beabsichtigt. Angenommen. ein Entwickler möchte in einer abgeleiteten Klasse eine geerbte Methode überschreiben. Beim Erstellen der Methode kann er diese Absicht durch eine entsprechende Annotation dem Compiler mitteilen. Wenn er nun bei der Überschreibung einen Fehler macht (z. B. einen kleinen Tippfehler, sodass eine neue zusätzliche Methode erstellt wird und die geerbte weiterhin sichtbar bleibt), dann kann der Compiler eine entsprechende Fehlermeldung ausgeben und somit einen eventuell schwer zu findenden Bug zur Laufzeit verhindern.
31 32
Beispiel
33 34 35
Annotationen erkennt man im Quelltext an einem vorangestellten @-Zeichen. Allerdings sollte man sie nicht mit den JavaDoc-Tags verwechseln, die zur Unterstützung von automatisch generierter Dokumentation dienen (mehr zu JavaDoc im Anhang). Diese Tags sind zwar in gewisser Weise auch Annotationen, beziehen sich aber nur auf Kommentare und dürfen nur innerhalb dieser verwendet werden.
36 37
Annotationen hingegen sind echte Bestandteile des Quelltextes und werden vom Java-Compiler bzw. der Virtual Machine berücksichtigt. Es gibt eine Handvoll vordefinierter Annotationen, aber es besteht auch die Möglichkeit, eigene Annotationen zu definieren.
38 39
Vordefinierte Annotationen 40
Es gibt zahlreiche vordefinierte Annotationen, die nach ihrer Funktion in verschiedenen Paketen definiert sind.
41
Im Paket java.lang finden sich Annotationen, mit denen Sie selbst erstellten Quelltext näher kennzeichnen können: ■
42
@Deprecated: wird einer Methodendeklaration vorangestellt und zeigt an, dass diese Methode als veraltet anzusehen ist. Der Compiler gibt eine Warnung aus, wenn eine solche Methode verwendet wird.
1097
Index
41.1
Annotationen
■
■
@Override: wird einer Methodendeklaration vorangestellt und zeigt an, dass diese Methode eine geerbte Methoden überschreiben soll. Wenn dies nicht der Fall ist, erzeugt der Compiler eine Fehlermeldung. @SuppressWarnings: alle Compiler-Warnungen bei dem nachfolgenden Sprachelement (z. B. Klasse) werden unterdrückt.
Das folgende Beispiel zeigt eine als veraltet markierte Methode berechneErwartungswert(), die eine gleichnamige geerbte Methode überschreibt. @Deprecated @Override public void berechneErwartungswert(double[] w) { // ... }
Das Paket java.lang.annotation enthält Annotationen, die zur Definition von eigenen Annotationen dienen: ■ ■
■
■
Neu in Java 6
Im Paket javax.annotation finden sich Annotationen, die insbesondere im »Enterprise Java«-Umfeld verwendet werden können, um dem ausführenden J2EE-Container weitere Informationen zur Ausführung zukommen zu lassen. ■ ■ ■
1098
@Documented: wird einer anderen Annotation vorangestellt und besagt, dass diese durch ein Tool (z. B. javadoc) dokumentiert wird. @Inherited: wird einer Klassendeklaration vorangestellt und zeigt an, dass alle Annotationen dieser Klasse auch an davon abgeleitete Klassen weitervererbt werden. @Retention(Typ): wird einer anderen Annotation vorangestellt und legt fest, wo diese Annotation verfügbar sein soll. Hierfür kann man als Typ folgende Werte einsetzen: ■ RetentionPolicy.SOURCE (verfügbar auf Quelltextebene) ■ RetentionPolicy.CLASS (Quelltextebene und in Class-Datei) ■ RetentionPolicy.RUNTIME (Quelltext, Class-Datei, Laufzeit) Wenn (Typ) weggelassen wird, gilt RetentionPolicy.CLASS. @Target(Typ): wird einer anderen Annotation vorangestellt und zeigt an, um welchen Elementtyp es sich handelt, auf den sich die andere Annotation bezieht. Typ ist optional; mögliche Werte sind u. a.: ElementType.CONSTRUCTOR, . ElementType.FIELD, ElementType.LOCAL_VARIABLE, ElementType.METHOD, ElementType.PACKAGE, ElementType.PARAMETER, ElementType.TYPE (Klassen, Annotationen).
@Generated: kann zur Kennzeichnung von Code verwendet werden, der automatisch generiert worden ist. @Resource(name=xxx) und @Resources: dienen zur Kennzeichnung von Ressourcen, die von der Anwendung benötigt werden. @InjectionComplete: dient zur Markierung einer Methode, die nach Bereitstellung aller notwendigen Ressourcen ausgeführt werden muss (und bevor andere Methoden der Klasse aufgerufen werden dürfen).
Selbst definierte Annotationen
■
■
@PostConstruct: dient zur Markierung einer Methode, die nach der Erstellung einer Klasse ausgeführt werden muss, bevor die Klasse verwendet werden darf. @Predestroy: dient zur Markierung einer Methode, die vor dem Vernichten einer Klasseninstanz (Entfernen aus dem Container) ausgeführt werden muss.
29
Hinzukommen weitere spezielle Annotationen für die Erstellung von Webservices, siehe Kapitel 43.
30 41.2
Selbst definierte Annotationen
31
Die Definition eigener Annotationen für spezielle Erfordernisse ist vor allem für Framework- und Toolentwickler interessant und sprengt daher den Rahmen dieses Buches. Im Folgenden werden wir daher nur die wesentlichen Grundlagen streifen und anhand einiger Beispiele zeigen, wie Annotationen definiert werden.
32
33
Die Definition erfolgt ähnlich wie bei einer Schnittstelle mit dem Schlüsselwort @interface1 und wird in einer gleichnamigen .java Datei abgespeichert, z. B.:
34
public @interface SpezialAnno { }
35
Eine derart definierte Annotation ist eine bloße Markierung, die man nun beliebigen Sprachelementen wie Klassen, Methoden oder Variablen voranstellen kann:
36
@SpezialAnno public class Demo { }
37
Wenn man als Autor einer Annotation die Verwendung der Annotation genauer vorgeben möchte, kann man die Annotation durch andere (vordefinierte oder selbst definierte) Annotationen näher definieren. Wenn wir beispielsweise SpezialAnno nur für Methoden erlauben wollen, dann schreiben wir:
38
39
import java.lang.annotation.*;
40
@Target(ElementType.METHOD) public @interface SpezialAnno { }
41
Die oben gezeigte Verwendung von SpezialAnno in der Klasse Demo verursacht nun einen Compiler-Fehler.
42 1
Die Verwendung des Schlüsselworts interface statt eines völlig neuen Begriffs annotation o. ä. wird nachvollziehbar, wenn man weiß, dass Annotationen intern vom Compiler in Schnittstellen übersetzt werden.
1099
Annotationen
Die eingesetzte @Target-Annotation ist ein Beispiel für Annotationen mit Parametern. Dies können wir auch bei SpezialAnno einführen. Listing 41.1: SpezialAnno.java – Definition einer eigenen Annotation import java.lang.annotation.*; @Target(ElementType.METHOD) public @interface SpezialAnno { String value(); String name() default "unbekannt"; }
Wir haben nun zwei Parameter value und name eingeführt. value hat dabei eine besondere Bedeutung: er ist der verwendete Parameter, wenn beim Einsatz der Annotation nur ein Wert ohne den Namen des Parameters übergeben wird. Für den Typ eines Parameters sind im Wesentlichen nur die üblichen primitiven Datentypen, sowie enum, String und Class möglich. Außerdem kann auch ein Array von einem dieser Datentypen verwendet werden. Parametern kann man auch einen Vorgabewert vorgeben, indem man das Schlüsselwort default und den gewünschten Wert hinzufügt. Wenn beim Einsatz einer Annotation für einen Parameter kein Wert übergeben wurde, wird der Vorgabewert eingesetzt. Im obigen Beispiel hat value keinen Vorgabewert, d.h., dieser Parameter muss übergeben werden. Unsere Annotation SpezialAnno könnte nun folgendermaßen eingesetzt werden: Listing 41.2: Demo.java – verwendet eine selbst definierte Annotation public class Demo { @SpezialAnno("Wichtig") public void sortieren() { // ... }
// value= "Wichtig" name=”unbekannt”
@SpezialAnno(value="Unwichtig", name="indexBerechnen") public void indexBerechnen() { // ... } }
Wie bereits erwähnt, sind selbst definierte Annotationen vor allem für Framework/API-Entwickler interessant (beispielsweise kommen bei EJB 3.0 Annotationen umfangreich zum Einsatz). Aber auch für den typischen Anwendungsprogrammierer lassen sich nützliche Anwendungen vorstellen. Beispiel
1100
Angenommen Sie möchten Ihren Java-Code mit einem Copyright-Vermerk versehen, um bei Bedarf später nachweisen zu können, dass generierter Bytecode (also Class-Dateien) Ihr geistiges Eigentum sind. Das Einfügen von Copyright-Vermerken in einen Kommentar der Klasse bringt hier keine Abhilfe, da Kommentare nicht mehr im Bytecode erscheinen.
Selbst definierte Annotationen
// Copyright 2006, Dirk Louis und Peter Müller public class SuperKlasse { // ...
Eine elegante Lösung dieses Problems ist die Definition einer @Copyright Annotation. Listing 41.3: Copyright.java – definierte eine Annotation für Copyright-Erklärungen
29
import java.lang.annotation.*; @Target(ElementType.TYPE) public @interface Copyright { String text() default "Copyright"; String jahr(); String name(); }
30
Mit dieser Annotation können wir nun alle Klassen und Schnittstellen mit einem Copyright-Vermerk versehen, der auch im Bytecode erscheint:
32
Listing 41.4: Demo.java – verwendet die Copyright-Annotation
33
31
@Copyright(jahr="2006", name="Dirk Louis/Peter Müller") public class Demo { // ...
34 35
36 37
38
39
40 41 42
1101
Inhalt
42 XML 29 30
Wahrscheinlich haben Sie hier und da schon einmal von XML gehört. Die Abkürzung steht eigentlich für extensible markup language, aber mittlerweile versteht man darunter häufig auch diverse andere Technologien, die eng mit dem eigentlichen XML zusammenhängen.
31
Das vorliegende Kapitel führt Sie in den Aufbau von XML-Dokumenten ein und zeigt Ihnen, wie Sie in Java XML-Dokumente verarbeiten können.
33
Einführung
XML ist eine sogenannte Markup-Sprache, d.h., sie dient zur strukturierten Darstellung von Daten. Die sicherlich bekannteste Markup-Sprache ist HTML, in der Webseiten geschrieben sind. Ein wesentliches Merkmal von Markup-Sprachen sind Schlüsselworte, die in spitzen Klammern stehen und Tags genannt werden. Von HTML kennen Sie diverse Tags, z.B.
34 35
15.1 Was ist XML?
36 37
An diesem Beispiel sehen Sie auch gleich, dass Tags in der Regel paarweise auftreten (z. B. ) analog zu einer öffnenden und schließenden Klammer. HTML ist eine der ältesten Markup-Sprachen, wozu also wurde noch XML entwickelt? Der Grund liegt darin, dass HTML primär dafür gedacht ist, Daten zu formatieren, damit sie für den Menschen besser lesbar sind. Im obigen Beispiel wird dafür gesorgt, dass die Überschrift vom Browser fett formatiert wird und somit für den Leser als solche direkt erkennbar ist. Viel schwieriger wird dies aber, wenn die obige HTML-Datei von einem Programm maschinell verarbeitet werden soll. Wie soll das Programm die Struktur des Inhalts – z. B. Überschriften – verstehen? HTML eignet sich daher nur sehr bedingt zur Beschreibung der Semantik, sodass die Verarbeitung durch Programme sehr schwierig wird.
38 39 40 41
42
Viel einfacher wäre es doch, wenn die Datei selbst beschreibt, was die einzelnen Bestandteile bedeuten. Hier schlägt nun die Stunde von XML:
1103
Index
42.1
32
XML
Was ist XML?
Mithilfe von geeigneten Tags wird eine hierarchische Struktur aufgebaut, die sich maschinell viel besser auswerten lässt als HTML. Und während man bei HTML aus einer Menge an fest definierten Tags auswählen muss, kann man bei XML alle notwendigen Tags selbst frei festlegen! Im Unterschied zu HTML sind XML-Tags case-sensitiv, d.h., es wird Großund Kleinschreibung unterschieden. HALT
XML hat sich innerhalb weniger Jahre zu einem Standardformat für Daten entwickelt, die in erster Linie von Programmen ausgewertet werden, z. B. Konfigurationsdateien großer Softwarepakete und als Austauschformat für EDI (electronic data interchange). Beachten Sie, dass ein XML-Dokument nichts darüber aussagt, wie es zur Anzeige formatiert werden soll. Für viele Anwendungen ist dies auch nicht notwendig. Es gibt jedoch auch Möglichkeiten, XML in HTML zu übersetzen. Zum Lesen und Auswerten (Parsen) sind mittlerweile viele Software-Werkzeuge verfügbar und auch für das Erstellen von eigenen Java-Programmen mit XML-Komponente existieren mehrere APIs, die Sie in diesem Kapitel kennenlernen werden.
42.2 Aufbau von XML-Dokumenten Der Grundbaustein, aus dem XML-Dokumente aufgebaut werden, ist das Element.
42.2.1 Elemente Ein Element kann in zwei Formen auftreten: ■
■
Falls das Element einen Inhalt haben soll (weitere Elemente oder Text, dann wird es durch ein Start- und Ende-Tag festgelegt, z. B. Java . Das Ende-Tag unterscheidet sich dabei vom Start-Tag nur durch den '/'. Inhaltlose Elemente werden durch ein abgeschlossenes Tag dargestellt: das Tag endet mit einem '/' . z. B. . In der Regel haben diese Elemente zusätzliche Attribute – wie im obigen Beispiel das Attribut aktiv.
Der Inhalt eines Elements ist alles zwischen Start- und Ende-Tag. Es kann sich hierbei um weitere Elemente handeln, um Text oder um beides. Da Ele1104
Aufbau von XML-Dokumenten
mente als Subelemente eines anderes Elements auftreten können, entsteht eine Hierarchie. Das Element, das in dieser Hierarchie ganz oben steht, ist das Wurzelelement (root). Außerdem kann jedes Element mit Attributen versehen werden, denen per Gleichheitszeichen Werte (eingeschlossen in Hochkommata) zugewiesen werden können. Ein Beispiel: Was ist XML? Wir wollen uns in diesem Kapitel mit XML befassen.
29
In diesem Fall ist Buch das Wurzelelement und enthält das Element Kapitel, das wiederum die Elemente Überschrift und Absatz enthält. Überschrift hat zudem ein Attribut nummer. Der Inhalt von Absatz ist der Text "Wir wollen ...", der Inhalt von Kapitel sind die Elemente Überschrift und Absatz.
32
30 31
33
Die hierarchische Struktur der Elemente eines XML-Dokuments lässt sich als Baum darstellen. Für das obige Beispiel haben wir eine sehr einfache Baumstruktur mit Buch als Wurzel, Kapitel als Kind von Buch und Überschrift und Absatz als Kinder von Kapitel:
34 35 Abbildung 42.1: Die Baumstruktur der Elemente
Buch
36 37
Kapitel
38
39 Überschrift
Absatz
40 41
Obwohl man von Baum und Wurzel spricht, wird beim Zeichnen der Baum umgedreht, sodass die Wurzel oben ist!
42
TIPP
1105
XML
42.2.2 Die XML-Deklaration Neben den Elementen kann ein XML-Dokument noch optional eine besondere Kopfzeile enthalten, die XML Deklaration genannt wird:
Wichtig ist hier vor allem das Attribut encoding, mit dem festgelegt wird, welche Zeichencodierung das XML-Dokument verwendet, beispielsweise "iso-8859-1" für lateinische Sprachen (Latin-1) oder "utf-16" für Unicode (also 2 Byte pro Zeichen) und "utf-8" für Unicode mit variabler Größe (1 Byte für ASCII-Zeichen, sonst 3 oder mehr Byte). Standardwert ist in XML "utf-8". Wenn das Attribut nicht angegeben wird, versucht der Parser die richtige Zeichencodierung zu erraten (was häufig erfolgreich ist).
42.2.3 Textinhalt Der Textinhalt eines Elements wird vom XML-Parser geparst (Typ PCDATA). Dadurch ist es möglich, in diesem Text verstreut weitere Elemente einzubauen. Allerdings müssen deshalb alle Zeichen, die in XML eine besondere Bedeutung haben (< , >, ", ', &), einzeln durch einen Stellvertreter – vordefinierte Entität genannt – ersetzt werden, damit der Parser nicht aus dem Tritt kommt. Tabelle 42.1: Vordefinierte Entitäten
Zeichen
vordefinierte Entität
Dezimal-Nummer
<
<
<
>
>
>
"
"
"
'
&apos
'
&
&
&
Beispiel: Die Rechtsform der GmbH & Co. KG
Falls dies zu unpraktisch ist, kann man auch einen Zeichenblock definieren, der vom Parser komplett ohne weitere Interpretation gelesen wird. Hierzu dient der Typ CDATA. Solche CDATA-Abschnitte beginnen mit , z.B.
1106
Aufbau von XML-Dokumenten
42.2.4 Processing Instructions Die Syntax markiert eine sogenannte Processing Instruction (PI). Ein PI definiert etwas, das von dem verarbeitenden Programm (i. d. R. ein XMLParser) ausgewertet wird. PI sind eigentlich dazu gedacht, andere Programme aufzurufen, werden aber nur selten eingesetzt.
29 42.2.5 Kommentare Außerdem können Sie natürlich reichlich Kommentare in ein XML-Dokument packen; deren Syntax lautet:
30
31
Kommentare werden vom XML-Parser komplett ignoriert und nicht an das aufrufende Programm weitergereicht.
32
42.2.6 Wohlgeformtheit
33
Ein XML-Dokument, das die folgenden Anforderungen erfüllt, ist wohlgeformt (well-formed) : Es gibt nur ein Wurzelelement. Jedes Element besteht aus Start- und Ende-Tag oder wird durch ein abgeschlossenes Tag dargestellt. Ineinander verschachtelte Elemente mit Start- und Ende-Tags werden in umgekehrter Reihenfolge geschlossen (d.h., eine Folge der Art kommt nicht vor). Werte für Attribute sind in Hochkommata (" ") eingeschlossen.
34
Im Gegensatz zu HTML schreibt die XML-Spezifikation zwingend vor, dass ein XML-Dokument wohlgeformt sein muss. In der Praxis bedeutet dies, dass ein XML-Parser beim kleinsten Fehler im Dokument aussteigt und eine Fehlermeldung liefert.
37
■ ■ ■
■
Diese Pingeligkeit von XML-Parsern hat einen wichtigen Grund. Es erleichtert die Implementierung von XML-Parsern, sodass diese sehr schlank sein können und wenig Speicherplatz brauchen (ein wichtiges Argument für PDA und sonstige mobile Geräte). Die in Webbrowsern integrierten HTML-Browser sind sehr tolerant, aber dafür auch etliche Mbyte große Module.
35
36
38
39 TIPP
40
Ergänzend kann es zu einem XML-Dokument eine besondere Datei geben, in der eine Grammatik der verwendeten Elemente definiert wird – die also festlegt, wie die Tags bzw. die Elemente eingesetzt werden dürfen. (Beispielsweise ob ein Absatz überhaupt innerhalb eines Elements Kapitel auftauchen darf?). Eine solche Grammatik nennt man Dokumenttyp-Deklaration (kurz DTD) . Ein äquivalenter Ersatz für Dokumenttyp-Deklarationen sind XML Schemata. Beide Formen werden wir im Folgenden näher betrachten.
41
42
1107
XML
TIPP
Es ist nicht zwingend erforderlich, eine Dokumenttyp-Deklaration oder ein Schema zu definieren. Es kann somit sowohl alleinstehende XML-Dokumente geben als auch XML-Dokumente mit DTD oder Schema. Ein wohlgeformtes XML-Dokument, das die Regeln einer zugehörigen DTD bzw. eines XML Schemas erfüllt, bezeichnet man als ein gültiges XML-Dokument.
42.3 DTD und XML Schema Wie bereits erwähnt, ist in XML die Menge der Tags und wie sie miteinander kombiniert werden dürfen, nicht festgelegt. Daraus folgt aber auch, dass ein Programm keine Möglichkeit hat zu testen, ob ein gegebenes XML-Dokument nur zulässige Tags in der richtigen Kombination verwendet. Falls ein solcher Test gewünscht ist, müssen folglich die notwendigen Regeln irgendwie festgelegt werden. Hierfür dient eine Art Grammatik, die auf zwei Arten festgelegt werden kann: als Dokumenttyp-Deklaration oder als XML Schema.
42.3.1 DTD Die klassische Variante heißt Dokumenttyp-Deklaration (DTD). Das folgende Beispiel zeigt ein XML-Dokument mit integrierter DTD: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
]> .... .. ....
Die Dokumenttyp-Deklaration beginnt in Zeile 2 mit . Zunächst wird mittels DOCTYPE das Wurzelelement benannt, hier Buch. Danach folgen in den Zeilen 3 und 4 die Elementdefinitionen: das Element Buch besteht aus einer beliebigen (*) Anzahl von Elementen des Typs »Kapitel«. Das Element Kapitel wiederum besteht aus einem Element Überschrift und beliebig vielen Elementen vom Typ Absatz. In Zeile 5 wird für Überschrift dann noch festgelegt, dass ein Attribut nummer obligatorisch ist.
1108
DTD und XML Schema
Wir können im Zuge dieses Buches nicht allzu tief in die Erstellung von DTDs einsteigen. Für die meisten Aufgaben sollten aber die folgenden Erläuterungen ausreichen: Elementdeklarationen Elementdeklarationen haben die allgemeine Form:
29
Mögliche Werte für (content-model) sind EMPTY (kein Inhalt), ANY (beliebiger Inhalt) oder eine Liste (elem1,elem2, ...) mit den Namen der Elemente, die als Subelemente (Kinder) auftreten müssen. Die Häufigkeit kann dabei mit Zusätzen beim jeweiligen Subelement verdeutlicht werden ( * : null oder mehr; + : eins oder mehr; ? : null oder eins).
30 31
Ferner gibt es noch den gemischten Modus (mixed-mode), bei dem Elemente und Text gemischt auftreten können. In diesem Fall hat die Elementdeklaration die Form:
32
02: 03:
34
Zeile 1 legt fest, dass das Element Kapitel ein Subelement Überschrift hat, gefolgt von einem oder mehr Elementen vom Typ Absatz. Zeile 2 legt für das Element Java-Code fest, dass es lediglich aus Text besteht. Zeile 3 sagt aus, dass Absatz aus beliebig vielen Sektionen besteht, die entweder Text oder das Element Java-Code sind.
35
36 37
Attributdeklarationen Attributdeklarationen haben die allgemeine Form:
38
Die einzelnen Attributdefinitionen folgen der Struktur:
39
derName derTyp dieDefault-Deklaration
Die Anwendung lässt sich am besten an einem Beispiel veranschaulichen:
40
41
42
1109
XML
Die erste Spalte der Attributdeklaration bestimmt den Namen, danach folgt der Typ. Mögliche Typen sind ■ ■ ■ ■
CDATA = normaler Text ID = normaler Text, der aber nur einmal vorkommen darf (JA|NEIN) Aufzählung mit den Werten "JA" und "NEIN" ("NEIN" ist der Standardwert, wenn das Attribut nicht explizit gesetzt wird.) NMTOKEN = Zeichenkette ohne Sonderzeichen
Die dritte Spalte zeigt an, ob das Attribut gesetzt werden muss (#REQUIRED = obligatorisch, #IMPLIED = optional) oder ein Standardwert eingesetzt wird ("JA"). Attributnamen sind frei wählbar mit einer Ausnahme: der Name darf nicht mit »xml« (in allen Kombination von Groß-/Kleinbuchstaben) beginnen. HALT
Angabe der DTD Die DTD kann zu Beginn eines XML-Dokuments definiert werden (nach der Kopfzeile) wie im obigen Beispiel oder in einer separaten Datei (z. B. buch.dtd). Dann beginnt das XML-Dokument folgendermaßen: 00: 01: 02: 04: 03:
...
Die DTD kann man aber auch durch einen Webserver zur Verfügung stellen; in diesem Fall würde nach SYSTEM ein voller URL eingesetzt.
42.3.2 XML Schemata DTDs haben den Nachteil, dass sie in einer eigenen Syntax erstellt werden müssen, die man sich als Entwickler dann natürlich auch erst mal wieder aneignen muss. Außerdem gibt es nur eine magere Auswahl an Datentypen und modular aufgebaut sind sie auch nicht. XML Schemata lösen diese Probleme elegant, indem sie selbst reine XMLDokumente sind. Es gibt zahlreiche Datentypen (z. B. string, float, integer) und man kann eigene Typen definieren. XML Schema ist noch kein Bestandteil der XML-Spezifikation. XML Schemata sind jedoch dabei, die Dokumenttyp-Deklarationen weitgehend abzulösen.
1110
XML-Dokumente parsen
42.3.3 Namensräume (Namespaces) Da jeder Autor von XML-Dokumenten die benötigten Tags selbst definieren kann, besteht die Gefahr, dass verschiedene XML-Dokumente die gleichen Namen für Tags verwenden. Solange XML-Dokumente nur lokal eingesetzt werden, ist dies natürlich kein Problem, aber die Lage ändert sich, wenn beispielsweise Programme Daten im XML-Format austauschen wollen.
29
Das bewährte Rezept im Programmierbereich sind Namensräume. Auch in Java gibt es so etwas in Form der Pakete. Dadurch können Klassen den gleichen Namen haben, aber dennoch zusammen in einem Programm verwendet werden, z. B. gibt es zwei Date-Klassen: java.sql.Date und java.util.Date, die durch Voransetzen des entsprechenden Paketnamens eindeutig identifiziert werden können.
30 31
In XML wird analog vorgegangen: Vor den eigentlichen Namen eines Tags setzt man ein frei wählbares Kürzel als Präfix und einen Doppelpunkt, z.B.
32
....
33
Das vorangestellte Präfix ist ein Kürzel für den verwendeten Namensraum. Dieser wird zu Beginn eines XML-Dokuments als ein Attribut des Wurzelelements definiert:
34
Hierdurch wird das Präfix MeinFormat als Kürzel des Namensraums mit dem URL http://www.carpelibrum.de definiert. Diesen URL muss es nicht wirklich geben; er dient nur zur eindeutigen Benennung des Namensraums.
35
36
Im Zusammenhang mit Namensräumen gibt es einen festen Begriff: »XML Anwendung«. Darunter versteht man einen Namensraum und eine festgelegte Menge von Elementen. Ein bekanntes Beispiel für eine XML Anwendung ist XHTML, mit dem HTML-Code erstellt werden kann.
37
Allen neuen und zukünftigen XML-Technologien wird direkt in der Spezifikation schon ein fester Namensraum zugewiesen. Beispielsweise können JavaServer Pages auch in einer XML-Form erstellt werden. Die Namensraumdefinition lautet:
38
39
d.h., alle Tags beginnen mit dem Präfix jsp.
40
42.4 XML-Dokumente parsen
41
Aus Sicht eines Java-Programmierers sind wir natürlich vor allem daran interessiert, XML-Dokumente zu parsen, d. h. sie einzulesen, in ihre Bestandteile zu zerlegen und mit diesen Daten zu arbeiten. Hierfür benötigt man einen Parser (bzw. Parserklassen), die es in zwei Varianten gibt:
42
1111
XML
■
■
Validierend: Der Parser überprüft, ob das XML-Dokument wohlgeformt ist und der Grammatik eines Dokumenttyp-Deklaration bzw. XML Schemas genügt. Nicht validierend: Der Parser prüft nur, ob das XML-Dokument wohlgeformt ist.
Zum Einsatz von Parsern bietet Java zwei grundlegende Techniken an: DOM (Document Object Model) und SAX (Simple API for XML). Sie stellen den Kontakt zum Parser und seinen Resultaten her. Außerdem gibt es noch weitere APIs, beispielsweise JDOM. Über DOM und SAX hat Sun noch eine weitere API gesetzt: JAXP (Java API for XML). Sie dient dazu, diejenigen Aspekte zu standardisieren, die bei DOM und SAX unterschiedlich gehandhabt werden, insbesondere das Erzeugen eines Parser-Objekts zu einer gegebenen XML-Quelle. JAXP stellt somit die Schnittstelle zum Parser her, der das XML-Dokument schließlich verarbeiten soll, und bietet daher eine sogenannte Factory-Klasse zum Erzeugen des eigentlichen Parsers an. Der Vorteil ist, dass die vom Programm zu verwendende Parserimplementierung ausgetauscht werden kann (z. B. statt des im JDK mitgelieferten Sun-Parsers den Apache Xerces Parser), ohne im Quelltext noch Änderungen vornehmen zu müssen. Es ist daher ratsam, DOM oder SAX in Verbindung mit JAXP zu verwenden.
42.4.1 SAX SAX (Simple API for XML) verwendet ein sequenzielles Vorgehen beim Parsen des XML-Dokuments. Der Inhalt des Dokuments wird von Anfang bis Ende durchlaufen, immer auf der Lauer liegend, ob ein bestimmtes Ereignis (event) auftritt. Die möglichen Ereignisse sind z. B. »Dokument beginnt«, »Element beginnt«, »Element endet« usw. Wenn ein Ereignis eintritt, wird es an ein spezielles Objekt vom Typ ContentHandler weitergereicht. Dieses Objekt bietet passende Methoden – sogenannte Callback-Methoden – an, um die Ereignisse zu verarbeiten. Es ist daher Aufgabe des Programmierers, diese Methoden zu implementieren. Einen SAX-Parser erzeugen Der erste Schritt beim Einsatz von SAX besteht darin, mithilfe von JAXP eine Instanz des Parsers zu besorgen. Hierfür müssen die SAX-Pakete org.xml.sax und org.xml.sax.helpers sowie das JAXP-Paket javax.xml.parsers importiert werden. Dann kann es losgehen: try { SAXParserFactory fa = SAXParserFactory.newInstance(); // fa.setValidating(true); falls validierender Parser notwendig SAXParser sax = fa.newSAXParser(); sax.parse(datei, new MeinSax()); } catch(Exception e) { System.err.println(" Fehler: " + e); }
1112
XML-Dokumente parsen
Bei der Beschaffung des Parsers hilft die Factory-Klasse SAXParserFactory, deren statische Methode newInstance() eine Factory-Instanz zurückliefert. Falls ein validierender Parser benötigt wird, kann dies mithilfe von setValidating() festgelegt werden (ohne diesen Aufruf wird kein validierender Parser erstellt). Danach können Sie durch Aufruf von newSAXParser() ein SAXParser-Objekt erzeugen. Das Parsen selbst beginnt dann mit dem Aufruf von parse(). Diese Methode erwartet ein File-Objekt mit dem XML-Dokument und eine Instanz einer Klasse, die das Interface ContentHandler implementiert.
29 30
Die try-catch Klammerung ist wegen verschiedener möglicher Exceptions erforderlich (IOException, ParserConfigurationException).
31 Callback-Methoden implementieren Da die zu implementierende Schnittstelle ContentHandler1 eine ganze Reihe von Methoden besitzt, von denen meistens nur einige benötigt werden, gibt es eine Adapter-Klasse DefaultHandler, die diese Schnittstelle bereits implementiert. Sie können sich dann darauf beschränken, nur die benötigten Methoden zu überschreiben.
32
33
Alle Callback-Methoden sind public und können eine SAXException auslösen. Die wichtigsten Methoden für das Parsen sind: Methode
Beschreibung
void startDocument()
Diese Methode wird nur einmal und als Erstes aufgerufen.
void endDocument()
Einmaliger Aufruf, wenn das Ende des Dokuments erreicht worden ist.
34 Tabelle 42.2: Wichtige SAX-Callback Methoden
35
36 37
void startElement(String namespace,String Der Beginn eines Elements ist erreicht worden. Parameter sind der Namensraum, der lokale Name des lokal, String voll,Attributes attr) Elements (ohne Präfix des Namensraums), der volle Name mit Präfix sowie die Attribute des Elements (aber ohne Attribute mit #IMPLIED-Wert).
38
void endElement()
Das Ende eines Elements ist erreicht worden. Gleiche Parameter wie startElement().
39
void characters(char[] ch, int start, int länge)
Der Parser hat Zeichendaten (normaler Text oder CDATA-Abschnitt) angetroffen und im Feld ch ab Position start abgelegt, Anzahl Zeichen ist länge.
40
void ignorableWhitespace(char[] ch, int start, int länge)
Der Parser hat ignorierbare Leerzeichen angetroffen und im Feld ch abgelegt. Leerzeichen sind bspw. ignorierbar, wenn laut DTD kein Text im aktuellen Element sein darf. Ein validierender Parser muss diese Methode aufrufen, ein nicht validierender kann auch characters() hierfür aufrufen.
1
41
42
org.xml.sax.ContentHandler ist natürlich nicht äquivalent zu java.net.ContentHandler.
1113
XML
Tabelle 42.2: Wichtige SAX-Callback Methoden (Forts.)
Methode
Beschreibung
void error(SAXParseException e)
Beim Parsen wurde ein Fehler, beispielsweise ein Verstoß gegen die Dokumenttyp-Deklaration, gefunden.
void fatalError(SAXParseException e)
Es wurde ein schwerwiegender Fehler gefunden. Der Parse-Vorgang wird abgebrochen.
Bei der Methode startElement() werden alle Attribute (außer diejenigen mit einem #IMPLIED-Wert, die nicht explizit im Start-Tag des Elements auftauchen) in einem besonderen Attributes-Objekt bereitgestellt. Es verfügt u. a. über die folgenden Methoden. Tabelle 42.3: Methoden der Schnittstelle Attribute
Methode
Beschreibung
String getValue(String vollerName)
Liefert den Wert des Attributs mit Namen vollerName.
String getValue(int index)
Liefert den Wert des Attributs an Position index (1. Attribut: index = 0)
int getLength()
Die Anzahl der abgelegten Attribute in diesem Objekt
String getQName(int index)
Liefert den vollqualifizierten Namen von Attribut Nr. index.
Damit wurden Ihnen alle wesentlichen Zutaten für die Verarbeitung eines XML-Dokuments vorgestellt. Schauen wir uns nun das Ganze in einem Beispiel an. Als Versuchsobjekt dient uns das folgende, kleine XML-Dokument: Listing 42.1: buch.xml – XML-Datei mit integriertem DTD ]> XML XML ist eine sogenannte Markup-Sprache. XML hat sich zu einem Standardformat entwickelt.
1114
XML-Dokumente parsen
Der erste Schritt beim Einsatz von SAX liegt darin, mit Hilfe von JAXP eine Instanz des Parsers zu erhalten:
In diesem Beispiel haben wir als Zeichencodierung ISO-8859-1 gewählt (erste Zeile), da einige Editoren die Standardcodierung von XML, nämlich UTF-8 nicht korrekt verarbeiten können. Java selbst jedoch hat mit utf-8 keine Probleme.
29 30 31 32 TIPP
33
Diese Datei wollen wir nun mit eine SAX-Parser einlesen und auswerten. Jedes Element soll inklusive seiner Attribute angezeigt sowie die Gesamtzahl an Elementen ermittelt werden.
34
Listing 42.2: SaxDemo.java – Programm zum Parsen eines XML-Dokuments mit SAX 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
import import import import
35
javax.xml.parsers.*; org.xml.sax.*; org.xml.sax.helpers.*; java.io.*;
36
public class SaxDemo extends DefaultHandler { int anzahlElemente = 0;
37
public static void main(String[] args) { System.console().printf("\n");
38
if(args.length != 1) { System.console().printf(" Falsche Anzahl Argumente\n"); System.console().printf(" Aufruf mit \n"); System.exit(0); }
39
40
try { File datei = new File(args[0]); SAXParserFactory fa = SAXParserFactory.newInstance(); fa.setValidating(true); SAXParser sax = fa.newSAXParser(); sax.parse(datei, new SaxDemo()); } catch(Exception e) { System.err.println(" Fehler: " + e); }
41
42
}
1115
XML
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 }
// die überschriebenen Methoden von DefaultHandler public void startDocument() { System.console().printf(" Das Parsen beginnt! \n"); } public void endDocument() { System.console().printf("\n\n"); System.console().printf(" Anzahl gefundener Elemente: " + anzahlElemente); } public void startElement(String namespace,String lokal, String voll, Attributes attr) { System.console().printf("\n"); System.console().printf(" Element: " + voll + " "); anzahlElemente++; int anz = attr.getLength(); if(anz > 0) { for(int i = 0; i < anz; i++) { System.console().printf("\t Attribut " + attr.getQName(i) + " = " + attr.getValue(i)); } } } public void error(SAXParseException e) { System.err.println(" Fehler gefunden: " + e); } public void fatalError(SAXParseException e) { System.err.println(" Fataler Fehler gefunden: " + e); }
Die Klasse SaxDemo wird wie besprochen von DefaultHandler abgeleitet. In den Zeilen 20-23 wird dann über ein Factory-Objekt ein validierender SAXParser erzeugt und mit parse() gestartet. Als Parameter übergeben wir die XML-Datei und eine neu erzeugte Instanz der Klasse, in der die CallbackMethoden definiert sind (hier die Klasse SaxDemo selbst). Immer, wenn der Parser auf ein Element trifft, wird unsere Implementierung von startElement() aufgerufen (Zeile 40-55), die den Namen des Elements ausgibt sowie die Liste der angetroffenen Attribute. Außerdem zählen wir in der Variablen anzahlElemente mit, wie viele Elemente angetroffen werden, sodass in der Methode endDocument() am Ende des Parse-Prozesses die Gesamtzahl ausgegeben werden kann.
1116
XML-Dokumente parsen
Spielen Sie ruhig ein bisschen mit dem XML-Dokument herum. Bauen Sie Fehler ein, entfernen Sie z. B. ein notwendiges Attribut, oder sorgen Sie dafür, dass ein Element eine andere Reihenfolge an Elementen enthält als in dem Dokumenttyp-Deklaration definiert. Beobachten Sie, ob und welche Ausgabe von error() ausgegeben wird.
TIPP
29
Wie Sie sehen können, ist ein SAX-Parser praktisch, um ein XML-Dokument von Anfang bis Ende sequenziell zu durchlaufen und bei den Elementen, die man braucht, geeignete Aktionen durchzuführen. Weit schwieriger ist es, zu schon besuchten Elementen zurückzukehren. Dann kommt man nicht umhin, die Elemente in einer selbst aufgebauten Datenstruktur zu speichern. Aber genau für diesen Fall gibt es DOM, dem wir uns jetzt zuwenden.
30 31
42.4.2 DOM
32
Das Document Object Model (DOM) ist ein Standard, der vom W3C (World Wide Web Consortium) entwickelt worden ist. Im Gegensatz zu SAX ist die Grundidee von DOM, das XML-Dokument komplett einzulesen und im Hauptspeicher in einer hierarchischen Datenstruktur (als Baum) abzubilden. Dadurch kann sehr elegant auf den Knoten operiert werden. Hier liegt allerdings auch der wesentliche Nachteil von DOM: für sehr große XMLDokumente ist es unter Umständen nicht brauchbar! Wenn ein Programm beispielsweise sehr schnell nur das erste Subelement der Wurzel benötigt (was in den ersten Zeilen der Datei zu finden ist), muss mit dem DOMAnsatz dennoch das gesamte Dokument eingelesen und geparst werden. Und bei extrem großen XML-Dokumenten kann unter Umständen der verfügbare Speicher schlichtweg zu klein sein. Aber das dürfte eher selten vorkommen.
33 34 35
36 37
DOM repräsentiert wie schon gesagt ein XML-Dokument als einen Baum von Knoten, wobei jeder Knoten die Wurzel eines darunter anschließenden UnterBaums sein kann.
38
Das hat u. a. den Vorteil, dass man alle Knoten einheitlich behandeln kann, inklusive die Wurzel des Gesamtbaums. Das typische Vorgehen ist meist der Aufruf einer rekursiven (also sich selbst aufrufenden) Methode, die bei der Wurzel startet und sich selbst für jeden Unterknoten (auch Kind genannt) der Wurzel aufruft.
39
40
Ein DOM einlesen 41
DOM wird im Paket org.w3c.dom definiert, was somit in allen DOMProgrammen importiert werden muss. Außerdem ist das JAXP-Paket javax.xml.parsers erforderlich2.
2
42
Man kann DOM natürlich auch ohne JAXP einsetzen.
1117
XML
Ein DOM wird durch ein Objekt vom Typ Document repräsentiert und entsprechend heißt das notwendige Parser-Objekt DocumentBuilder. Der Aufbau eines DOM umfasst analog zu SAX den Einsatz einer Factory-Klasse, die das eigentliche Parser-Objekt generiert. Der Ablauf ist folgender: 1.
Ein Parser-Objekt vom Typ DocumentBuilder wird erzeugt. DocumentBuilderFactory fa = DocumentBuilderFactory.newInstance(); fa.setValidating(true); // falls validierender Parser gewünscht DocumentBuilder db = fa.newDocumentBuilder();
2.
Die XML-Datei wird dem Parser-Objekt übergeben, um ein Document zu erzeugen. Document doc = db.parse(datei); //
Die obigen Methoden können im Fehlerfall Exceptions auslösen (IOException, ParserConfigurationException), sodass eine try-catch Klammerung erforderlich ist. Zugriff auf Knoten und ihre Daten Weiter oben haben wir gesagt, dass ein Objekt vom Typ Document das erzeugte DOM repräsentiert. Jetzt müssen wir etwas präziser werden: Document selbst ist abgeleitet von Node, welches die zentrale Schnittstelle zur Darstellung eines Knotens ist. Document repräsentiert also eine spezielle Art von Knoten, nämlich die Wurzel. Diesen Knoten erhält man durch die Document-Methode getDocumentElement(). In dem Baum, der vom DOM-Parser erzeugt wird, bilden Elemente, Attribute, Text usw. die Knoten als Instanzen von Node. Jeder Node hat hierbei einen Typ, je nachdem, was er repräsentiert. Die wichtigsten Typen sind Tabelle 42.4: Konstanten für mögliche Knotentypen
Konstante
für
short Node.DOCUMENT_NODE
Dokument (Wurzel)
short Node.ELEMENT_NODE
Element
short Node.ATTRIBUTE_NODE
Attribut
short Node.CDATA_SECTION_NODE
CDATA Text
short Node.TEXT_NODE
normalen Text (PCDATA)
short Node.COMMENT_NODE
Kommentare
Außerdem enthält Node eine Reihe von Methoden, um auf Knoteninformationen zuzugreifen.
1118
XML-Dokumente parsen
Methode
Beschreibung
String getNodeName()
Name des Knotens
short getNodeType()
Typ des Knotens, z. B. ELEMENT_NODE
String getNodeValue()
Wert des Knotens
NamedNodeMap getAttributes()
Liefert eine Liste der Attribute, wenn der Knoten vom Typ ELEMENT_NODE ist, sonst null.
Tabelle 42.5: Ausgewählte Node-Methoden zum Ermitteln von Knoteninformationen
29 30
Die Methode getNodeName() ist etwas erklärungsbedürftig. Sie liefert für einen Knoten, der ein Element repräsentiert, den Namen des Elements (z. B. Kapitel). Andernfalls wird ein generischer Name eingesetzt, z. B. #text für einen Knoten vom Typ TEXT_NODE oder #cdata-section beim Typ CDATA_SECTION_NODE.
31 32
Auch die Methode getNodeValue() ist nicht so einfach einzusetzen. Sie liefert den Wert eines Knotens zurück, aber was soll das genau sein? Nun, der Wert hängt vom Knotentyp ab: Steht das Node-Objekt für ein Attribut (Typ Node.ATTRIBUTE_NODE), ist der Wert des Knotens der Wert des Attributs; ist es vom Typ Node.CDATA_SECTION_NODE, dann ist der Wert die Zeichenfolge aus der CDATA-Sektion. Analoges gilt für Kommentare und Text; alle anderen Knoten haben den Wert null (insbesondere auch der Typ Node.ELEMENT).
33 34 35
Die Methode getAttributes() liefert die Attribute in ungeordneter Form in einem besonderen Objekt vom Typ NamedNodeMap. Zum Auslesen dienen die Methoden:
36
int getLength() – liefert die Anzahl der Attribute Node item(int n) – liefert das n-te Attribut als Node Node getNamedItem(String name) – liefert das Attribut name als Node
37
Machen wir uns an einigen Beispielen klar, welche Knoten vom DOM-Parser erzeugt werden. Für die XML-Sequenz
38
Die Grundlagen von XML
39
■ ■ ■
wird der DOM-Parser zwei (Baum-)Knoten anlegen. Einen Knoten vom Typ ELEMENT_NODE zur Darstellung des Elements Überschrift, und einen Knoten vom Typ TEXT_NODE für den Inhalt des Elements, nämlich die Zeichenkette "Die Grundlagen von XML". Außerdem wird noch ein Knoten vom Typ ATTRIBUTE_NODE für das Attribut nummer erstellt, der dem Elementknoten für Überschrift angegliedert ist.
40 41
42
1119
XML
TIPP
Der Knoten für ein Attribut gehört nicht zum eigentlichen Gesamtbaum, d.h., er ist kein Kind des jeweiligen Elementknotens und somit nicht über getChildNodes() erreichbar. Man kann diese Knoten nur über getAttributes() erreichen. Etwas komplizierter wird die Lage, wenn wir annehmen, dass Überschrift noch was anderes enthält, z. B. ein weiteres Element oder einen CDATAAbschnitt, z.B. Die Grundlagen von XML & Co. ]]>
Als gutmütiger Mensch könnte man glauben, dass in diesem Fall (abgesehen vom Attributknoten) drei Knoten angelegt werden: einer für Überschrift, einer für den Text "Die Grundlagen ..." und einer für den CDATAAbschnitt. Dem ist auch so, aber es wird noch ein Knoten angelegt vom Typ TEXT_NODE mit dem generischen Namen #text! Der Grund: Nach dem Ende des CDATA-Abschnitts folgt ein Text-Abschnitt bis zum Ende des Elements Überschrift. Allerdings besteht er lediglich aus einigen Leerzeichen, aber das ist dem Parser egal. Er legt hierfür ebenfalls einen Knoten an. Nachdem wir nun wissen, welche Knoten es gibt und was sie enthalten, brauchen wir noch eine Möglichkeit, im Baum herumzuwandern und Knoten zu besuchen. Hierzu werden von Node folgende Methoden angeboten: Tabelle 42.6: Node-Methoden zum Navigieren im Knotenbaum
Methode
Beschreibung
boolean hasChildNodes()
Liefert true, wenn der Knoten Kinder hat.
NodeList getChildNodes()
Liefert ein NodeList Objekt mit den Kindern des Knotens.
Node getFirstChild()
Liefert das erste Kind des Knotens.
Node getLastChild()
Liefert das letzte Kind des Knotens.
Node getParentNode()
Liefert den Vorgänger im Baum (der Vater).
Node getNextSibling()
Liefert das nächste Kind des Vaterknotens, also ein Geschwister des aktuellen Knotens.
Node getPreviousSibling()
Liefert das vorhergehende Kind des Vaterknotens, also das vorhergehende Geschwister des aktuellen Knotens.
Die wichtigste Methode ist getChildNodes(), die ein Objekt der Hilfsklasse NodeList zurückliefert. NodeList wiederum bietet die folgenden Methoden zum Zugriff auf die Kinder an:
1120
XML-Dokumente parsen
■ ■
int getLength() – liefert die Anzahl Node item(int n) – liefert Kind n als Node-Objekt
Nach dieser großen Fülle an trockenen Details wird es Zeit, DOM im Einsatz zu erleben. Das folgende Beispiel liest ein XML-Dokument ein und gibt die Elementknoten aus. Außerdem wird die Gesamtzahl an XML-Elementen ermittelt.
29
Listing 42.3: DOMDemo.java – Programm zum Durchlaufen eines XML-Dokuments mit DOM 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
import javax.xml.parsers.*; import org.w3c.dom.*; import java.io.*;
30 31
public class DOMDemo { static int anzahlElemente = 0; static int anzahlKnoten = 0;
32 public static void main(String[] args) { System.console().printf("\n");
33
if(args.length != 1) { System.console().printf(" Falsche Anzahl Argumente"); System.console().printf(" Aufruf mit "); System.exit(0); }
34 35
try { File datei = new File(args[0]); DocumentBuilderFactory fa = DocumentBuilderFactory.newInstance(); DocumentBuilder db = fa.newDocumentBuilder(); Document doc = db.parse(datei);
36 37
// von der Wurzel aus durch den Baum laufen // 'doc.getDocumentElement()' liefert die Wurzel unterbaumDurchlaufen(doc.getDocumentElement());
38
System.console().printf("\n\n"); System.console().printf(" Anzahl Knoten : %d\n", anzahlKnoten); System.console().printf(" Anzahl Elemente: %d\n", anzahlElemente); } catch(Exception e) { System.err.println(" Fehler: " + e); }
39
40 41
} // rekursiv alle Knoten im Unterbaum mit Wurzel 'knoten' static void unterbaumDurchlaufen(Node knoten) { if(knoten == null) return;
42
1121
XML
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 }
anzahlKnoten++; if(knoten.getNodeType() == Node.ELEMENT_NODE) { anzahlElemente++; System.console().printf("\n"); System.console().printf(" Element %s", knoten.getNodeName()); // Attribute ausgeben NamedNodeMap attribs = knoten.getAttributes(); int anz = attribs.getLength(); for(int i = 0; i < anz; i++) { Node tmp = attribs.item(i); System.console().printf("\t Attribut " + tmp.getNodeName() + " = "+tmp.getNodeValue()); } } // die Kinder besuchen NodeList kinder = knoten.getChildNodes(); if(kinder == null) return; int anzahl = kinder.getLength(); for(int i = 0; i < anzahl; i++) unterbaumDurchlaufen(kinder.item(i)); }
Zunächst beginnen wir mit dem Erzeugen eines DOM-Parsers und legen dann das DOM durch Aufruf von parse() an (Zeilen 19-23). Rückgabewert von parse() ist ein Objekt vom Typ Document, welches die Wurzel des generierten Baums repräsentiert. Um nun alle Knoten zu besuchen, rufen wir in Zeile 27 die rekursive Methode UnterbaumAbklappern() mit der Wurzel als Startwert auf. UnterbaumAbklappern() macht bei jedem Aufruf Folgendes: Zunächst wird für den übergebenen Knoten geprüft, ob er vom Typ ELEMENT_NODE ist und falls ja, werden sein Name und seine Attribute ausgegeben und der Zähler für die Anzahl der Elemente erhöht. Damit ist der übergebene Knoten abgearbeitet. Nun beginnt der rekursive Teil. In Zeile 66 besorgen wir uns die Liste der Kinder dieses Knotens und rufen dann für jedes Kind die Methode erneut auf (Zeile 73-73). Ein DOM aufbauen und schreiben Nachdem Sie nun gesehen haben, wie aus einer XML-Datei ein DOM wird, bleibt noch die Frage zu klären, wie der umgekehrte Weg funktioniert: Man hat ein DOM im Speicher (entweder vom Programm komplett selbst erzeugt 1122
XML-Dokumente parsen
oder ein eingelesenes DOM, das modifiziert worden ist) und will daraus nun eine XML-Datei erzeugen. Das Anlegen eines zunächst leeren DOM verläuft wie für den Fall, dass wir aus einer XML-Datei einlesen, mit Ausnahme des Aufrufs von parse(): DocumentBuilderFactory fa = DocumentBuilderFactory.newInstance(); DocumentBuilder db = fa.newDocumentBuilder(); Document doc = db.newDocument();
29
Da es nichts zu parsen gibt, wird stattdessen mit der Methode newDocument() von DocumentBuilder ein leeres Dokument angelegt.
30
Leeres Dokument heißt auch ein leerer Baum. Wir müssen also nun damit beginnen, die Mutter aller Knoten – die Wurzel – anzulegen:
31
Element wurzel = doc.createElement("Buch"); doc.appendChild(wurzel);
32
Zum Erzeugen von Knoten bietet die Schnittstelle Document die Methode createElement() an, die als Parameter den gewünschten Elementnamen erwartet und ein Objekt Element zurückgibt. Mit appendChild() kann dann der Knoten dem DOM hinzugefügt werden. Dies macht man natürlich nur mit dem Knoten, der als Wurzel dienen soll. Die restlichen Knoten werden direkt den entsprechenden Knoten hinzugefügt.
33 34
Da es nicht nur Elementknoten geben kann, bietet Document natürlich eine ganze Reihe von Methoden zum Erzeugen an. Methode
Beschreibung
Element createElement(String name)
Erzeugt einen Elementknoten.
Comment createComment(String kom)
Erzeugt einen Kommentarknoten.
CDATASection createCDATASection(String cdata)
Legt einen CDATA-Knoten an.
Text createTextNode(String text)
Legt einen Text-Knoten (PCDATA) an.
35 Tabelle 42.7: Document-Methoden zum Erzeugen von Knoten
37
38
Die mit diesen Methoden erzeugten Knotenobjekte sind alle vom allgemeinen Typ Node abgeleitet und somit vollwertige Knoten mit allen lesenden Zugriffsmethoden, die wir schon kennengelernt haben.
39
40
Da Elemente noch Attribute enthalten können, brauchen wir noch einen Weg, um ein Objekt vom Typ Element mit Attributen zu versehen bzw. vorhandene zu entfernen. Hierzu dienen die Methoden von Element. Methode
Beschreibung
void removeAttribute(String name)
Löscht das Attribut name.
void setAttribute(String name, String wert)
Setzt das Attribut name auf den wert.
String getAttribute(String name)
Liefert den Wert des Attributs name.
36
41 Tabelle 42.8: Element-Methoden für Attribute
1123
42
XML
Es wurde bereits erwähnt, dass Knoten mit der Methode appendChild() zu einem anderen Knoten als Kind hinzugefügt werden. Darüber hinaus gibt es in Node noch weitere Methoden zum Ändern bzw. Ersetzen von Knoten: Tabelle 42.9: Node-Methoden zum Ändern/ Hinzufügen von Kindern
Methode
Beschreibung
Node appendChild(Node kn)
Fügt kn an als Kind (hinter das letzte vorhandene) hinzu. Rückgabewert ist kn.
Node insertBefore(Node kn, Node kind)
Fügt kn vor dem existierenden kind ein. Rückgabewert: kn.
Node removeChild(Node kn)
Löscht kn aus der Liste der Kinder. Rückgabewert: kn.
Node replaceChild(Node neu, Node alt)
Ersetzt das Kind alt durch neu. Rückgabewert: alt.
Alle diese Methoden können eine DOMException auslösen. Außerdem gibt es noch die hilfreiche Node-Methode Node cloneNode(boolean tief)
Sie erstellt eine identische Kopie des Knotens mit Ausnahme der Referenz auf den Vaterknoten: getParent() liefert bei der Kopie null. Wenn als Parameter true übergeben wird, wird eine sogenannte tiefe Kopie erstellt, d.h., der gesamte Unterbaum, der an diesem Knoten dranhängt, wird kopiert. Wenn ein DOM erstellt bzw. ein bestehender modifiziert worden ist, sollte zum Abschluss noch folgende Anweisung durchgeführt werden: // Document doc; doc.getDocumentElement().normalize();
Die Methode normalize() bewirkt eine Kompression des DOM und die Knoten werden teilweise zusammengelegt. Wenn Sie beispielsweise zu einem Knoten zwei Knoten vom Typ Text hinzugefügt haben, entspricht dies nicht mehr der DOM-Spezifikation. (In einem XML-Dokument gibt es nicht zwei aufeinander folgende Text-Abschnitte, denn dies würde immer als ein TextAbschnitt aufgefasst.) Entsprechend kann es im DOM nicht zwei Text-Knoten hintereinander geben. Nun bleibt noch das Problem der Ausgabe. Wie schreibt man ein DOM als XML-Datei? Die DOM-Spezifikation hilft hier leider nicht weiter. Praktisch wäre eine Methode in der Art Document.writeToFile(), die es aber nicht gibt. Deshalb müssen wir uns selbst helfen. Zwei Vorgehensweisen sind denkbar: ■ ■
Sie schreiben eine Methode, die durch den Baum wandert, alle Knoten besucht und entsprechende Ausgaben erzeugt. Sie verwenden XSLT.
Der erste Ansatz ist im Prinzip schon oben in der rekursiven Methode unterbaumDurchlaufen() demonstriert. Die andere Möglichkeit betrifft XSLT3. Es 3
1124
steht für »extensible stylesheet language transformer«
XML-Dokumente parsen
handelt sich hierbei um eine XML-Anwendung, mit deren Hilfe man XMLDateien von einem Format in ein anderes Format umwandeln kann. Dies ist ein sehr umfangreiches Thema und würde daher den Rahmen dieser Einführung sprengen. Aber für unser Ziel, ein DOM als XML-Datei auszugeben, ist XSLT sehr interessant, da es hierfür das Paket javax.xml.transform und einige Unterpakete gibt, mit deren Hilfe dies möglich ist. Das folgende Codefragment demonstriert die grundsätzliche Vorgehensweise
29
01:import javax.xml.transform.*; 02:import javax.xml.transform.dom.*; 03:import javax.xml.transform.stream.*; 04:import java.io.File; 05: 06: // Doc erzeugen: doc = ... 07: Factory fac = TransformerFactory.newInstance(); 08: Transformer transformer = fac.newTransformer(); 09: transformer.setOutputProperty("encoding","iso-8859-1"); 10: transformer.setOutputProperty("indent","yes"); 11: DOMSource quelle = new DOMSource(doc); 12: File datei = new File("test.xml"); 13: 14: StreamResult ausgabe = new StreamResult(datei); 15: transformer.transform(quelle, ausgabe);
30 31 32
33 34
Nachdem in Zeile 6 ein Document-Objekt in geeigneter Weise angelegt worden ist, kann in Zeile 7 eine Instanz von TransformerFactory angelegt werden, mit dessen Hilfe das eigentliche Umwandlungsobjekt vom Typ Transformer erstellt wird. Dieses Objekt hat eine Reihe von Eigenschaften, von denen wir zwei nützliche setzen, nämlich anstelle des standardmäßigen UTF-8 eine andere Zeichencodierung sowie den Hinweis, die Ausgabe einzurücken, damit sie besser lesbar ist. Damit sind wir schon fast fertig. Für den Umwandlungsprozess brauchen wir noch eine Quelle und ein Ziel. Als Quelle dient unser DOM, daher dient doc als Argument für den Konstruktor von DOMSource. Als Ziel brauchen wir noch eine Datei anzulegen; daraus wird dann ein besonderes Objekt StreamResult (Zeile 14) erstellt. Schließlich kommen wir mit transform() zur Umwandlung. Da wir keine Umwandlungsvorschriften definiert haben, wird 1:1 umgewandelt und wir haben unser Ziel erreicht: Das DOM ist in einer Datei gelandet.
35
36 37
38
39
42.4.3 JDOM
40
JDOM steht für »Java Document Object Model« und ist eine weitere API zur Verarbeitung von XML-Dokumenten. Nachdem Sie weiter oben schon SAX und DOM kennengelernt haben, werden Sie sich fragen, warum es nun noch ein Paket hierfür gibt.
41
42
Die Antwort liefert das »J« in JDOM. Diese API ist speziell auf Java ausgerichtet, im Gegensatz zu SAX und DOM. Hier handelt es sich um allgemeine Spezifikationen, die in mehreren Programmiersprachen vorhanden sind. JDOM liegt in gewisser Hinsicht eine Stufe über SAX und DOM und erlaubt 1125
XML
auch über Schnittstellen den vollen Zugriff auf diese APIs. JDOM verwendet intern Java-Datenstrukturen (z. B. Collections) zur Repräsentation eines DOM und bietet dabei eine bessere Performance und elegantere Zugriffsmöglichkeiten – beispielsweise ist das Schreiben eines XML-Baums in eine Datei direkt durch eine entsprechende Klasse möglich. Die im vorherigen Abschnitt beschriebenen Umwege sind nicht notwendig. JDOM gehört nicht zur Java-Standardbibliothek und muss separat bezogen und installiert werden. Wir werden an dieser Stelle lediglich einen groben Überblick über JDOM geben, damit Sie abschätzen können, ob JDOM etwas für Sie ist. Installation Die aktuelle Version des Pakets können Sie unter www.jdom.org finden. Kopieren Sie das JAR-Archiv in ein geeignetes Verzeichnis. Für das Kompilieren und Ausführen von Programmen mit JDOM muss die JAR-Datei in der Umgebungsvariablen CLASSPATH erscheinen. Falls Sie keinen Klassenpfad verwenden möchten, können Sie javac und java auch mit der Option -classpath .;c:\\jdom.jar ausführen. Paket-Übersicht Das JAR-Archiv stellt die JDOM-Klassen in Form von mehreren Paketen zur Verfügung: ■
■ ■ ■
org.jdom bildet den Kern mit allen Klassen, die zur Repräsentation des XML-Dokuments notwendig sind, z. B. gibt es Klassen für Element, Text, CDATA usw. org.jdom.input und org.jdom.output stellen die Funktionalität zum Lesen und Schreiben von XML-Dokumenten bereit. org.jdom.transform ist das Gegenstück zu javax.xml.transform und enthält Klassen zur Umwandlung von XML-Formaten. org.jdom.adapters enthält Adapter, um andere DOM-Implementierungen/-Parser zu nutzen.
Ein JDOM-Streifzug Einen guten Eindruck von JDOM erhalten Sie, wenn wir gleich voll hineinspringen und uns einige Codefragmente anschauen. Sie werden erkennen, dass der Übergang zu JDOM sehr einfach ist, wenn man mit DOM bereits vertraut ist. Schauen wir uns also an, wie ein XML-Dokument in den Hauptspeicher gelangt: import import import import
1126
org.jdom.*; org.jdom.input.*; org.jdom.output.*; java.io.*;
XML-Dokumente parsen
// ... SAXBuilder dom = new SAXBuilder(); Document doc = dom.build(new File(args[0]));
Hierzu bietet JDOM eine Klasse SAXBuilder an. Man erzeugt also einfach eine Instanz dieser Klasse und lässt anschließend die Methode build() das Document-Objekt erstellen (hier natürlich org.jdom.Document und nicht org.w3c.dom). Das war es schon!
29
Der Zugriff auf die einzelnen Bestandteile des Baums ist sehr ähnlich zu DOM. Es gibt Klassen für Element, Text, CDATA, Comment usw. und Methoden, um im Baum zu navigieren, Kinderknoten zu besuchen und Informationen abzufragen, z.B.
30 31
Element wurzel = doc.getRootElement(); System.console().printf(" Wurzel: %s\n", wurzel.getName());
32
java.util.List kinder = wurzel.getChildren(); System.console().printf(" Anzahl direkte Kinder: %d\n", kinder.size()); java.util.Iterator it = kinder.iterator();
33 Element tmp; while(it.hasNext()) { tmp = (Element) it.next(); System.console().printf("\n"); System.console().printf(" Kind: %s, Text: %s \n", tmp.getName(), tmp.getText()); }
34
Im Gegensatz zu der DOM-API kommen überwiegend Java-Klassen zur Anwendung, beispielsweise liefert getChildren() eine Instanz von java. util.List und keine gewöhnungsbedürftige NamedNodeMap wie im DOMGegenstück. Das erleichtert den Einsatz von JDOM enorm, da man nicht viele neue Klassen kennenlernen muss und alle vertrauten Java-Vorteile wie Iteratoren einsetzen kann.
36
35
37
38
Auch die Ausgabe als XML-Datei lässt sich ohne große Umschweife in drei Zeilen durchführen – mithilfe einer Instanz von XMLOutputter:
39
FileWriter fw = new FileWriter(args[1]); XMLOutputter ausgabe = new XMLOutputter(); Format format = ausgabe.getFormat(); format.setEncoding("iso-8859-1"); ausgabe.setFormat(format); ausgabe.output(doc, fw);
40 41
Den vollständigen Quelltext finden Sie als JDomDemo.java auf der Buch-CD.
42
DISC
1127
Inhalt
43 Webservices 43 Webservices (oder Webdienste) sind eine der neueren Errungenschaften der IT-Welt. Was genau unter einem Webservice zu verstehen ist, und wie man Webservices in Java programmieren kann, ist Thema dieses Kapitels.
Neu in Java 6
44 45
Seit Java 6 gehört die API zur Erstellung von Webservices zum Umfang der Standard Edition.
46 TIPP
43.1
Was ist ein Webservice?
47
Allgemein ausgedrückt, ist ein Webservice eine Software-Anwendung, die eine klar begrenzte Funktionalität – einen Service (Dienst) – realisiert und über das Internet (oder auch nur das firmeninterne Intranet) zur Verfügung stellt. Webservices werden in zunehmenden Maße zur losen und flexiblen Kopplung von IT-Anwendungen eingesetzt, gerade im Zusammenhang mit dem neuen Schlagwort SOA (Service-oriented Architecture). Hierunter versteht man eine an den Geschäftsprozessen des Unternehmens ausgerichtete IT-Landschaft, bei der die vielen verschiedenen IT-Systeme ihre Kernfunktionalitäten als Webservices anbieten, die je nach Bedarf zu aggregierten Superservices zusammengefügt werden können. Webservices sind grundsätzlich nicht für menschliche Benutzer gedacht (wie normale Webseiten), sondern nur für andere Software-Anwendungen, die üblicherweise via HTTP den Webservice aufrufen und auf die Antwort warten. Die Nachrichten, die dabei ausgetauscht werden (also der Aufruf und die Antwort), sind XML-basiert und müssen einem bestimmten XMLFormat entsprechen, das durch eine besondere Spezifikationssprache namens WSDL definiert wird. In welcher Form und was zwischen Sender und Empfänger ausgetauscht wird, wird außerdem noch durch ein weiteres Protokoll namens SOAP1 festgelegt.
Ursprünglich eine Abkürzung von »Simple Object Access Protocol«. Aus lizenzrechtlichen Gründen ist man mittlerweile davon abgerückt. SOAP ist nun einfach ein Name und keine Abkürzung mehr.
1129
Index
1
Webservices
Abbildung 43.1: Webservice-Aufruf
Das Bindeglied zwischen dem Aufrufer (Konsument) eines Webservices und dem Anbieter des Webservices bildet das zugehörige WSDL-Schema, das genau festlegt, wie der technische Name des Service lautet, welche Parameter übergeben werden und wie die Antwort aussieht. Das klassische Vorgehen beim Einsatz von Webservices mit Java ist daher die Definition des WSDL und davon ausgehend dann die Implementierung geeigneter Java-Klassen. Hierbei helfen zahlreiche Tools und Bibliotheken (beispielsweise Apache Axis). Neu in Java 6
Aus der Sicht eines Java-Programmierers wäre es aber eigentlich natürlicher, von den Java-Klassen auszugehen, d.h., man definiert eine Klasse mit Methoden, die als Webservice aufrufbar sein sollen, und generiert daraus das WSDL, das man dann weitergeben kann, sodass andere Entwickler wissen, wie der Webservice aufgerufen werden kann. Genau dieser Ansatz wird seit Java 6 möglich durch den Einsatz von besonderen Annotationen, mit denen man Klassen und Methoden sehr rasch in einen Webservice umwandeln kann. Sie werden begeistert sein, wie einfach dies ist! Die folgenden Abschnitte sollen Ihnen daher ein erstes Reinschnuppern ermöglichen und Lust auf mehr machen.
43.2 Webservices definieren Als Beispiel wollen wir einen Webservice anbieten, der Lottozahlen zieht. Als Ausgangspunkt schreiben wir eine Java-Klasse Lotto: Listing 43.1: Lotto.java – Lottozahlen-Ziehung import java.util.*; public class Lotto { private Random rand; // Default-Konstruktor public Lotto() { rand = new Random(); }
1130
Webservices definieren
// anzahl Zahlen aus dem Bereich 1 bis maxWert ziehen Integer[] lottoZahlen(int anzahl, int maxWert) throws LottoException { Integer[] ergebnis = new Integer[anzahl]; try { HashSet zahlen = new HashSet(); int zaehler = 0;
43
44
while(true) { int zahl = rand.nextInt(maxWert+1) + 1; boolean eingefuegt = zahlen.add(zahl);
45
if(eingefuegt == false) // Zahl gibt's schon; neuer Versuch continue; else { zaehler++;
46
if(zaehler == anzahl) break;
47
} } // Array erzeugen und sortieren ergebnis = zahlen.toArray(ergebnis); Arrays.sort(ergebnis); } catch(Exception e) { LottoException ex = new LottoException(new Date(), e.getMessage()); throw ex; } return ergebnis; } }
Die Klasse bietet keine Überraschungen. Die Methode lottoZahlen() berechnet anzahl zufällige Zahlen aus dem Bereich 1 bis maxWert und liefert sie in aufsteigender Reihenfolge als Array zurück. Im Fehlerfall wird eine Exception erzeugt, wobei wir hier eine selbst definierte vom Typ LottoException verwenden: Listing 43.2: LottoException.java – Exception für Fehlermeldung von Webservice package de.carpelibrum; import java.util.Date; public class LottoException extends Exception { private String msg; private Date zeitPunkt;
1131
Webservices
// Konstruktor public LottoException(Date z, String msg) { super(); this.zeitPunkt = z; this.msg = msg; } public String getMessage() { return "Fehler um " + zeitPunkt.toString() + " : " + msg; } }
Nun werden wir daraus einen Webservice generieren. Am besten kopieren Sie sich den obigen Quelltext in einen Editor und folgen den Anweisungen. 1.
Um eindeutige Bezeichner für den Webservice zu erhalten, definieren Sie seine Klassen in einem eigenen Paket. Als Paketname für unser Beispiel wählen wir de.carpelibrum2. Stellen Sie eine entsprechende Zeile package de.carpelibrum; an den Anfang von Lotto.java und LottoException.java. 2. Die Umwandlung in einen Webservice erfolgt durch besondere Annotationen, die in den Paketen javax.ws und javax.ws.soap definiert sind. Sie müssen daher Lotto.java um entsprechende import-Anweisungen erweitern. 3. Markieren Sie die Klasse Lotto per Annotation als Webservice-Klasse: @WebService(name="Lotto", targetNamespace="http://www.carpelibrum.de/") @SOAPBinding(style=SOAPBinding.Style.RPC) public class Lotto {
@WebService, definiert den Namen und den Namespace (der zum Paket-Namen korrespondieren muss) und @SOAPBinding legt hier fest, dass Sie einen klassischen Aufruf-Stil verwenden, d.h., der Aufrufer übergibt ggf. Parameter und erhält eine Antwort mit dem Ergebnis. 4. Markieren Sie die Methode lottoZahlen() als die eigentliche Operation des Webservices: @WebMethod(operationName="lottoZahlen", action="urn:lottoZahlen") public @WebResult(partName="result") Integer[] lottoZahlen(@WebParam(name="anzahl") int anzahl, @WebParam(name="maxWert") int maxWert) throws LottoException {
@WebMethod definiert den Namen der Webservice-Operation, @WebResult zeigt an, dass ein Ergebnis zurückgeliefert wird, und @WebParam definiert die Namen von Parametern.
Bei diesem Vorgehen muss die Java-Klasse, die Sie in einen Webservice verwandeln möchten, folgende Bedingungen erfüllen: sie muss 2
1132
Denken Sie bitte daran, dass Sie die Dateien auch auf der Festplatte entsprechend ablegen müssen, z. B. de\carpelibrum\Lotto.java.
Webservices veröffentlichen
5.
public sein, einen Standardkonstruktor haben, aber keine finalize() Methode, und die zu veröffentlichende Methode muss ebenfalls public und nicht final sein. Zum Abschluss kompilieren Sie die Klassen: javac .\de\carpelibrum\Lotto.java
43
43.3 Webservices veröffentlichen
44
Jetzt können Sie daran gehen, den Webservice zu veröffentlichen. Dies erfolgt üblicherweise in einem Application-Server, aber für unsere Testzwecke reicht auch ein kleiner Miniserver, der praktischerweise seit Java 6 in Form der Klasse Endpoint mitgeliefert wird.
45
Listing 43.3: LottoServer.java – Miniwebserver zum Veröffentlichen des Webservices lottozahlen
46
import javax.xml.ws.Endpoint; import de.carpelibrum.*;
47
public class LottoServer { // Starte einen Webserver für diesen Service auf Port 8081 public static void main(String[] args) { System.out.println(); Lotto ls = new Lotto(); System.out.println("Starte Webservice 'lottoZahlen' " + "auf Port 8081..."); Endpoint.publish("http://localhost:8081/lottoZahlen", ls); } }
Nachdem Sie diesen Miniserver kompiliert haben, ist der Webservice fast fertig! Es fehlen lediglich noch einige Artefakte, die wir mit dem Hilfsprogramm wsgen erzeugen: wsgen -d . -cp . -wsdl de.carpelibrum.Lotto
Mit –d und –cp definieren wir das Zielverzeichnis bzw. den Klassenpfad (hier jeweils durch den Punkt das aktuelle Arbeitsverzeichnis). Der Schalter –wsdl ist optional und sorgt dafür, dass zusätzlich eine WSDL-Datei generiert wird. Nach Ausführung dieses Programms werden Sie feststellen, dass es in .\de\carpelibrum nun einen neuen Ordner jaxws gibt, der eine generierte Bean-Klasse für die LottoException beinhaltet. Sie wird beim Deployment des Webservices benötigt. Starten Sie nun den Webservice mit java LottoServer. Um zu testen, ob der Service überhaupt antwortet, öffnen Sie einen Webbrowser und geben http://localhost:8081/lottoZahlen?WSDL ein. Als Antwort sollten Sie das WSDL-Dokument sehen, das unseren Lottoservice definiert.
1133
Webservices
Abbildung 43.2: WSDL-Schema des Webservices
Nun fehlt nur noch ein Client, der den Service nutzt.
43.4 Webservices aufrufen Um ein Java-Programm zu erstellen, das den obigen Webservice aufruft, benötigen Sie ein passendes WSDL. Vermutlich ist es Ihnen bereits aufgefallen: der Aufruf von wsgen mit dem Parameter –wdsl hat bereits im Arbeitsverzeichnis die Datei LottoService. wsdl erzeugt, sowie zwei Schemadateien mit Typdefinitionen, die von LottoService.wsdl referenziert werden. Diese Dateien können Sie nun anderen Entwicklern bereitstellen, die damit eigene Client-Anwendungen zur Nutzung Ihres Webservices erstellen können. Um den Webservice auf dem lokalen Rechner zu testen, müssen Sie in der WSDL-Datei noch die lokale Adresse des Webservices eintragen. Öffnen Sie hierzu die Datei LottoService.wsdl und ersetzen Sie im Abschnitt
1134
Webservices aufrufen
die Zeichenkette REPLACE_WITH_ACTUAL_URL durch http://localhost:8081/ lottoZahlen. Nun können Sie mit dem Hilfsprogramm wsimport aus der WSDL-Datei die notwendigen Klassen generieren lassen: wsimport -d . -p de.carpelibrum.wsclient LottoService.wsdl
43
Mit –d definieren Sie das Zielverzeichnis (der Punkt steht wie üblich für das aktuelle Arbeitsverzeichnis) und mit –p den gewünschten Paketnamen für die generierten Klassen). Das Tool wsimport scheint noch einige Bugs zu haben und die Generierung von Java-Klassen aus einer WSDL-Datei funktioniert bei etwas komplexeren Fällen nicht immer! Falls Sie sich also intensiver mit Webservices beschäftigen, sollten Sie andere Werkzeuge (z. B. wsdl2java aus dem Apache Axis Framework http://ws.apache.org/axis/) einsetzen.
44
45
HALT
46
Das Ergebnis von wsimport finden Sie nun im Ordner .\de\carpelibrum\wsclient: Mehrere Java-Dateien, mit deren Hilfe Sie nun den Client programmieren können.
47
Listing 43.4: LottoClient.java – Beispiel zum Aufruf eines Webservices import de.carpelibrum.wsclient.*; import java.util.*; public class LottoClient { public static void main(String[] args) { System.out.println(); try { LottoService lottoService = new LottoService(); Lotto l = lottoService.getLottoPort(); IntArray ziehung = l.lottoZahlen(6,49); List zahlen = ziehung.getItem(); System.out.println(" Lottozahlen:\n "); for(Integer i : zahlen) System.out.print(" " + i); System.out.println(); } catch(Exception e) { System.out.println(e); } } }
Der Aufruf des Webservices erfolgt in den ersten Zeilen des try-Blocks. Zunächst wird eine Instanz der Klasse LottoService erzeugt. Dann besorgt sich der Client mithilfe der Methode getLottoPort() die Instanz für die eigentliche Webservice-Operation, über die dann die Methode lottoZah1135
Webservices
len() aufgerufen werden kann. Danach erfolgt die Ausgabe der erhaltenen Zahlen.
Wie Sie sehen können, ist der Aufruf des Webservices aus Client-Sicht einfach ein normaler Methodenaufruf. Man muss keine HTTP-Verbindung explizit aufbauen und Daten in einen OutputStream schreiben. Dies alles passiert hinter den Kulissen durch die generierten Klassen LottoService und Lotto sowie natürlich die Java Virtual Machine! Kompilieren und starten Sie den Client und füllen Sie gleich mal einen Lottoschein aus! Abbildung 43.3: Aufruf des Webservices
1136
Teil 7 Anhänge 1139
Die Java-Tools
44
1165
Installation von MySQL
45
1169
Zahlensysteme
46
1173
Tabellen
47
Inhalt
44 Die Java-Tools 43
44
Neben Laufzeitumgebung, Bibliotheken und Demobeispielen gehören zum Lieferumfang des JDK auch eine Reihe von nützlichen bis unentbehrlichen Hilfsprogrammen – allen voran Compiler (javac) und Interpreter (java). Als Java-Programmierer sollten Sie mit der Arbeitsweise und den Möglichkeiten dieser Werkzeuge vertraut sein, selbst wenn Sie sie nur mittelbar, etwa über den komfortablen Umweg einer integrierten Entwicklungsumgebung, nutzen. Die offizielle Dokumentation der JDK-Werkzeuge finden Sie in den JDK-Hilfedateien unter \docs\tooldocs. (Die gezippte Version der Hilfedateien können Sie von der Buch-CD kopieren oder von der Sun-Website herunterladen.)
45 46 47 TIPP
44.1 javac – der Compiler Er ist für den Entwickler unbestreitbar das wichtigste Werkzeug überhaupt – der Compiler. Mit strengem Blick achtet er auf die syntaktische Korrektheit unserer Quelltexte und weist mit belehrenden Fehlermeldungen zurück, was seinen Anforderungen nicht genügt. Er ist Tutor und Qualitätsmanager in einem und nur was seine Gnade findet, wird in maschinennahen, ausführbaren Bytecode umgewandelt. Das allein wäre ja noch nicht einmal so schlimm – als Programmierer ist man schließlich an den ständigen Kampf mit dem Compiler gewöhnt –, wenn dieser sich dafür wenigstens einfach und problemlos bedienen ließe. Einfach gestaltet sich die Arbeit mit dem Compiler jedoch nur, wenn Sie mit einer Entwicklungsumgebung mit integrierter Projektverwaltung arbeiten, die statt Ihrer für den korrekten Compiler-Aufruf sorgt (wie es z. B. der JBuilder von Borland tut), oder wenn alle Quelldateien zusammen in einem Verzeichnis stehen, sodass Sie nur in der Konsole zu dem betreffenden Verzeichnis wechseln und dort den Compiler aufrufen müssen – beispielsweise: javac *.java
1139
Index
Sobald Sie aber versuchen, die in der Entwicklungsumgebung voreingestellten Compiler-Optionen abzuändern, oder javac von der Konsole aus aufrufen, um Programme zu kompilieren, deren Quelltext auf unterschiedliche Verzeichnisse verteilt, in Pakete organisiert oder auf Class-Dateien von
Die Java-Tools
Drittanbietern angewiesen ist, zeigt sich, dass der Java-Compiler ohne tiefer gehende Kenntnisse seiner Optionen und Arbeitsweise kaum zu beherrschen ist.
44.1.1 Arbeitsweise Damit der Compiler eine Quelldatei in Bytecode übersetzen kann, müssen zwei Bedingungen erfüllt sein: ■ ■
der Quelltext muss syntaktisch korrekt sein für alle im Quelltext auftauchenden Typen (Klassen und Schnittstellen) müssen Definitionen vorliegen
Letzter Punkt ist für das Verständnis des Compilers von entscheidender Bedeutung. Während der Compiler Ihre Quelltextdatei parst, trifft er fortwährend auf Stellen, wo neue, ihm bis dato noch unbekannte Typen verwendet werden: dies kann z. B. die Instanziierung einer Klasse, die Ableitung von einer Basisklasse oder die Implementierung einer Schnittstelle sein. Um die betreffenden Stellen übersetzen zu können, braucht der Compiler die zugehörigen Typdefinitionen (plus etwaiger direkter und indirekter Basisklassen und Schnittstellen). Auf der Suche nach den gewünschten Informationen durchforstet er nacheinander: ■ ■ ■
die geladene Quelldatei(en)1 die Java-Standardbibliothek2 die Verzeichnisse und Archive (JAR und ZIP), die im Klassenpfad und/oder im Quellpfad eingetragen sind
Sofern Sie also in den zu kompilierenden Quelldateien Klassen oder Schnittstellen verwenden, die weder in den geladenen Quelldateien definiert sind, noch aus der Java-Standardbibliothek stammen, müssen Sie dem Compiler einen Klassen- und/oder einen Quellpfad angeben, der ihn zu den Dateien mit den gesuchten Typdefinitionen führt. Klassenpfad und Quellpfad EXKURS
1.
Der Klassenpfad kann auf zwei Ebenen definiert werden: global in Form der Umgebungsvariablen CLASSPATH (Informationen, wie Sie die CLASSPATH-Umgebungsvariable auf den verschiedenen Betriebssystemen
1
Wenn Sie beim Compiler-Aufruf mehrere Quelldateien angeben, z. B. javac Klasse1.java Klasse2. java Klasse3.java, werden diese zusammen in den Arbeitsspeicher geladen und geparst! Siehe hierzu auch Szenario 1 im Abschnitt »Aufrufe«. Die Java-Standardbibliothek ist fester Bestandteil der Java-Laufzeitumgebung. Wenn Sie die Laufzeitumgebung (JRE für Java Runtime Environment) zusammen mit dem JDK installieren, finden Sie die Dateien der Bibliothek im jre/lib-Unterverzeichnis der JDK-Installation. Die meisten Klassen der Bibliothek sind in der JAR-Datei rt.jar untergebracht. JAR-Dateien sind letzten Endes ZIP-Archive und können daher mit ZIP-Programmen wie z. B. WinZip (Download von www.winzip.com) geöffnet und eingesehen werden. (Wenn das ZIP-Programm den Datentyp .jar nicht unterstützt, müssen Sie im Öffnen-Dialog den allgemeinen Datentyp (*.*) auswählen.) Die Quelltextdateien der Java-Klassen sind in dem Archiv src.zip (oder src.jar) im Hauptverzeichnis des JDK versteckt.
2
1140
javac – der Compiler
einrichten, finden Sie in Kapitel 3.2, Abschnitt »Anpassen des Systems« und unter www.carpelibrum.de.) oder nur für den aktuellen CompilerAufruf in Form der Option -classpath. Wenn Sie -classpath verwenden, wird nur der im Aufruf angegebene Klassenpfad durchsucht. (Der in der Umgebungsvariablen festgehaltene Pfad wird also nicht erweitert, sondern temporär ersetzt!) Wenn Sie die classpath-Option nicht setzen, wird der globale Klassenpfad aus der Umgebungsvariablen durchsucht. Sollte die Umgebungsvariable CLASSPATH auf dem System überhaupt nicht definiert sein, besteht der globale Klassenpfad aus dem aktuellen Verzeichnis (aus dem heraus der Compiler aufgerufen wurde).
43
44
45
Der Klassenpfad wird grundsätzlich sowohl nach Quelltext- als auch nach Class-Dateien mit den gesuchten Typdefinitionen durchsucht. Es sei denn, Sie hätten einen Quelltextpfad angegeben; dann wird der Klassenpfad ausschließlich nach Class-Dateien durchforstet.
46
Einen Quelltextpfad können Sie nur beim Aufruf des Compilers angeben. Die zugehörige Option lautet -sourcepath. Die im Quellpfad stehenden Verzeichnisse und Archive werden ausschließlich nach Quelltextdateien durchsucht.
47
Wie Sie -classpath und -sourcepath sinnvoll einsetzen, erfahren Sie in den Abschnitten »Optionen« und »Aufrufe«. Wie wendet der Compiler die obigen Regeln bei der Kompilation Ihrer Quelltexte an? Wie geht er beispielsweise vor, wenn Sie eine Quelltextdatei Hauptfenster.java kompilieren, in der unter anderem folgende Typen verwendet werden: ... class Hauptfenster extends JFrame { JButton schalter1; MeinButton schalter2; ...
Zum Kompilieren öffnen Sie die Konsole, wechseln in das Verzeichnis der Quelltextdatei und rufen den Compiler folgendermaßen auf: javac Hauptfenster.java
Der Compiler lädt daraufhin die zu kompilierende Datei und parst sie. Dabei stößt er auf den Typ Hauptfenster. Er erkennt natürlich sofort, dass dieser Typ an der betreffenden Stelle gerade neu definiert wird. Zufrieden geht er die Datei weiter durch, bis er zwei Tokens3 weiter auf die Klasse JFrame trifft. JFrame stammt aus der Java-Standardbibliothek, die in dem Archiv rt.jar verpackt ist. Der Compiler öffnet das Archiv und lädt die gesuchte Definition aus der Datei JFrame.class. An sich könnte er jetzt, nachdem er 3
Als Token bezeichnet man die bedeutungstragenden Einheiten eines Quelltextes. Tokens sind: Bezeichner, Schlüsselwörter, Literale, Operatoren und Symbole.
1141
Die Java-Tools
die Definition zu JFrame aufgespürt hat, mit der nächsten Zeile weitermachen, doch ganz so schnell geht es nicht. Die Klasse JFrame besitzt eine Reihe von direkten und indirekten Basisklassen und Schnittstellen, deren Definitionen der Compiler ebenfalls aus der Standardbibliothek lädt. Hat er alle benötigten Informationen zusammengetragen, verfährt er in gleicher Weise für die Klasse JButton (wobei bereits geladene Class-Dateien nicht erneut geladen werden). Schließlich trifft er auf die Klasse MeinButton. Zuerst prüft er, ob sich nicht vielleicht in den geladenen, zu kompilierenden Quelltextdateien (im vorliegenden Fall also die Datei Hauptfenster.java) eine Definition findet. Falls nein, hält er in der Standardbibliothek Ausschau, d.h., er sucht im Archiv rt.jar nach einer Class-Datei, die den Namen des gesuchten Typs trägt – also MeinButton.class. Da er auch hier nicht fündig wird, bleiben nur noch Quellund Klassenpfad. Im Aufruf wurden weder ein Quell- noch ein Klassenpfad angegeben. Dafür sei auf dem System ein globaler Klassenpfad definiert: SET CLASSPATH=c:/java/libs;c:/projekte/libs.jar
Da kein Quellpfad angegeben wurde (siehe Kasten), durchsucht der Compiler die im Klassenpfad aufgeführten Verzeichnisse und Archive sowohl nach Class- wie auch nach Quelltextdateien, die den Namen des benötigten Typs tragen – also MeinButton.class oder MeinButton.java. Welche Art von Datei er dabei findet, bestimmt seine weitere Vorgehensweise: ■
■
■
Findet er eine entsprechende Class-Datei, ist er schnell fertig. Er lädt die Datei und prüft nur noch, ob die Klasse direkte oder indirekte Basisklassen und Schnittstellen besitzt. Falls ja, werden diese ebenfalls geladen. Findet er eine Quelltextdatei, wird diese nicht nur geladen, sondern auch kompiliert – mit der Konsequenz, dass er wiederum für alle in der Datei verwendeten Typen die zugehörigen Definitionen (samt Basisklassen und Schnittstellen) zusammensuchen muss. Findet er Class-Datei und Quelltextdatei, prüft er, ob die Quelltextdatei neueren Datums ist, als die Class-Datei. Wenn ja, bedeutet dies, dass es seit der letzten Kompilierung der Datei Änderungen im Quelltext gegeben hat, und der Compiler lädt die Quelltextdatei. Ansonsten lädt er die Class-Datei.
Nehmen wir an, die Klasse MeinButton ist in einer Quelldatei MeinButton.java definiert, die sich im gleichen Verzeichnis wie Hauptfenster.java befindet, sodass der Compiler die Datei nicht im gegebenen Klassenpfad findet. Welche Möglichkeiten bestünden dann, den Compiler-Aufruf so zu verändern, dass Hauptfenster.java wie gewünscht kompiliert wird? Eine Möglichkeit wäre, die Quelldatei MeinButton.java direkt beim Aufruf mit anzugeben. In diesem Fall findet der Compiler die Definition von MeinButton auf jeden Fall, da sie in den zu kompilierenden Quelltextdateien steht, die der Compiler ja zu Beginn alle zusammen lädt: javac Hauptfenster.java MeinButton.java
1142
javac – der Compiler
Die zweite Möglichkeit wäre, einen passenden Quellpfad anzugeben. Im vorliegenden Fall würde es reichen, im Quellpfad das aktuelle Verzeichnis (symbolisiert durch einen Punkt) aufzuführen. Der Compiler würde dann den Quellpfad nach Quelltextdateien und den globalen Klassenpfad nach Class-Dateien durchsuchen: javac -sourcepath . Hauptfenster.java
43
Natürlich können Sie auch den Klassenpfad ändern – beispielsweise indem Sie das aktuelle Verzeichnis in den globalen Pfad eintragen oder indem Sie den zu verwendenden Klassenpfad im Aufruf angeben:
44
javac -classpath . Hauptfenster.java
45
Benennung von Quelltextdateien Letzter Punkt ist wert, dass man ihn noch einmal extra hervorhebt: Wenn der Compiler im Klassen- und/oder Quellpfad nach einer Typdefinition sucht, erwartet er, dass die gesuchte Typdefinition in einer Datei steht, die wie der gesuchte Typ heißt. Sucht der Compiler also beispielsweise im Klassenpfad nach einer Schnittstelle IDemo, erwartet er für diese eine Datei IDemo.class oder IDemo.java4 zu finden. Täte er dies nicht, müsste er auf der Suche nach IDemo die Dateien im Klassenpfad nacheinander laden und parsen, bis er die gesuchte Typdefinition gefunden oder alle Dateien durchgearbeitet hat.
EXKURS
46 47
Die Class-Dateien Nachdem der Compiler die Definitionen der in der Quelldatei verwendeten Typen zusammengetragen hat, übersetzt er die in der Datei definierten Typen (Klassen und Schnittstellen) in Bytecode. Für jeden Typ erzeugt er dabei eine eigene Class-Datei, die den Namen der betreffenden Klasse oder Schnittstelle trägt. Enthält die zu kompilierende Datei zum Beispiel die Definition einer Schnittstelle ISortierbar und einer Klasse Vektor, so erzeugt der Compiler die Class-Dateien ISortierbar.class und Vektor.class. Der Grundsatz, dass jede Klasse in eine eigene Class-Datei kompiliert wird, gilt auch für innere Klassen (siehe Kapitel 10.4). Die Class-Dateien für innere Klassen werden nach dem Muster aussereKlasse$innereKlasse.class benannt. Anonyme innere Klassen, wie sie in Java häufig zur Ereignisbehandlung eingesetzt werden, werden vom Compiler intern durchnummeriert und nach dem Muster aussereKlasse$nummer.class benannt. public Hauptfenster() { ... // anonyme Lauscher-Klasse schalter.addActionListener(new ActionListener() { 4
Vorausgesetzt, es wurde kein Quellpfad angegeben.
1143
Die Java-Tools
public void actionPerformed(ActionEvent e) { schalter_actionPerformed(e); } });
In diesem Fall erzeugt der Compiler für das mit new erzeugte ActionListener-Objekt eine anonyme Klasse, die er in der Datei Hauptklasse$1.class speichert. Die erzeugten Class-Dateien speichert der Compiler standardmäßig in dem Verzeichnis der zugehörigen Quelldatei. Wenn Sie möchten, dass die ClassDateien in ein anderes Verzeichnis geschrieben werden, müssen Sie beim Compiler-Aufruf die Option -d verwenden und das gewünschte Verzeichnis angeben. javac -d c:\meineClassDateien quelldatei
Überhaupt gibt es eine ganze Reihe von Optionen, mit deren Hilfe die Arbeitsweise des Compilers beeinflusst werden kann.
44.1.2 Optionen Wie der Compiler seine Arbeit verrichtet, kann über verschiedene Optionen gesteuert werden. In integrierten Entwicklungsumgebungen geschieht dies in der Regel über die Dialoge der Projektverwaltung; wenn Sie allein mit dem JDK arbeiten und den Compiler über die Konsole aufrufen, geben Sie die gewünschten Optionen in der Befehlszeile an. javac Optionen Quelldateien
Tabelle 44.1: Die wichtigsten Compiler-Optionen
Option
Beschreibung
-classpath pfad
Gibt den Klassenpfad an, in dem der Compiler nach Typdefinitionen suchen soll. Die im Klassenpfad aufgeführten Verzeichnisse und Archive (JAR und ZIP) werden durch Semikolons (Doppelpunkt unter Linux) getrennt. Der Punkt ».« steht für das aktuelle Verzeichnis. Der im folgenden Aufruf angegebene Klassenpfad besteht aus dem aktuellen Verzeichnis, dem Verzeichnis c:\bin und dem JAR-Archiv klassen.jar, das unter C:\JAVA gespeichert ist.
javac -classpath .;c:\bin;c:\java\klassen.jar quelldatei.java Wenn Sie die classpath-Option setzen, wird der in der CLASSPATHUmgebungsvariablen angegebene Klassenpfad ignoriert. (Wenn Sie weder die Option noch die Umgebungsvariable gesetzt haben, besteht der Klassenpfad aus dem aktuellen Verzeichnis.) Wie im vorangehenden Abschnitt beschrieben, wertet der Compiler nicht nur im Klassenpfad stehende Class-Dateien aus, sondern auch Quelldateien (es sei denn, Sie setzen die Option -sourcepath, siehe weiter unten).
1144
javac – der Compiler
Option
Beschreibung
-d verzeichnis
Gibt das Verzeichnis an, in das die erzeugten Class-Dateien geschrieben werden. Für Klassen, die Teil eines Pakets sind, wird verzeichnis noch um den Paket-Pfad erweitert (siehe weiter unten, Szenario 5).
Tabelle 44.1: Die wichtigsten Compiler-Optionen (Forts.)
javac -d c:\klassen quelldatei.java Wird die Option nicht gesetzt, werden die Class-Dateien zusammen mit ihren Quelldateien gespeichert.
-deprecation
43
Zeigt an, wo veraltete Bibliothekselemente verwendet werden.
44
javac -deprecation quelldatei.java Voraussetzung ist allerdings, dass der Compiler etwas zum Kompilieren findet. Wenn Sie beispielsweise ein Projekt, das veraltete API-Elemente enthält, sonst aber fehlerfrei ist, normal kompilieren, gibt der Compiler eine Warnung aus, die auf vorhandene »deprecated« Elemente hinweist, erzeugt aber dennoch die gewünschten Class-Dateien. Wenn Sie daraufhin javac mit der Option -deprecation aufrufen, ohne zuvor die generierten Class-Dateien zu löschen, vergleicht der Compiler das Datum der Quelldateien mit dem Datum der Class-Dateien und erkennt, dass es keinen Grund gibt, die Dateien neu zu kompilieren. Folglich findet er auch keine veralteten API-Elemente und die Ausgabe bleibt leer.
-g
45
46 47
Nimmt Informationen für den Debugger in die Class-Dateien mit auf.
javac -g quelldatei.java -g:none
Nimmt keinerlei Debug-Informationen mit auf.
javac -g:none quelldatei.java Verwenden Sie diese Option zusammen mit -O für die abschließende Kompilation Ihrer fertigen Programme, Module etc.
javac -g:none -O quelldatei.java -help
Listet die Standardoptionen auf.
-nowarn
Unterbindet die Ausgabe von Warnungen.
-source 1.n
Kompiliert für die angegebene JDK-Version.
-sourcepath pfad
Gibt an, wo der Compiler nach Quelltextdateien suchen soll. Für die Zusammensetzung des »Quellpfades« gelten die gleichen Regeln wie für den Klassenpfad (siehe -classpath). Wenn der Compiler bei der Kompilation einer Quelltextdatei auf einen Typ (Klasse oder Schnittstelle) trifft, zu dem es weder in der Datei noch in der JavaStandardbibliothek eine Definition gibt, und Sie die Option -sourcepath nicht gesetzt haben, sucht der Compiler im Klassenpfad nach Class- und Quelltextdateien mit der benötigten Definition. Wenn Sie dagegen die Option -sourcepath setzen, sucht der Compiler im Klassenpfad nur nach Class-Dateien und in dem angegebenen Quellpfad nur nach Quelltextdateien.
1145
Die Java-Tools
Tabelle 44.1: Die wichtigsten Compiler-Optionen (Forts.)
Option
Beschreibung
-O
Optimiert den Quelltext. Verwenden Sie diese Option zusammen mit -g:none für die abschließende Kompilation Ihrer fertigen Programme, Module etc.
javac -g:none -O quelldatei.java Verwenden Sie diese Option auf gar keinen Fall während des Debuggens. Durch die Optimierung kann es nämlich zu erheblichen Differenzen zwischen Quelltext und Bytecode kommen – beispielsweise, wenn im Quelltext vorhandene Variablen durch die Optimierung wegfallen.
-verbose
Gibt während des Kompilierens Statusmeldungen aus.
-Xlint:deprecation
Gibt detaillierte Meldungen aus, wenn Klassenelemente verwendet werden, von deren Gebrauch mittlerweile abgeraten wird.
-Xlint:unchecked
Gibt detaillierte Meldungen aus, wenn ein parametrisiertes Objekt ohne Typisierung verwendet wird.
Eine vollständige Beschreibung aller Compiler-Optionen finden Sie in den JDK-Hilfedateien unter ..\docs\tooldocs. INFO
44.1.3 Aufrufe Wie der Compiler in einem konkreten Fall aufzurufen ist, hängt von einer Vielzahl von Faktoren ab: Soll eine einzelne Datei, eine Bibliothek oder ein Projekt kompiliert werden? Gehören die zu kompilierenden Typen in ein Paket? Welche Art von Quelldateien (Quelltext-, Class-, Ressourcendateien) werden verwendet und wo sind diese lokalisiert? Einige typische Aufrufe werden Ihnen im Folgenden vorgestellt. Die Komplexität der Aufrufe steigt von Szenario zu Szenario (von der Kompilation einfacher Programme, deren Quelltextdateien in einem Verzeichnis zusammengefasst sind, bis zur Erstellung umfangreicher Projekte, die Pakete, Drittbibliotheken und Ressourcendateien verwenden). Szenario 1: Quelltextdateien in einem Verzeichnis kompilieren Im einfachsten Fall stehen alle Quelltextdateien in einem Verzeichnis und abgesehen von den Klassen und Schnittstellen der Java-Standardbibliothek werden nur Klassen/Schnittstellen verwendet, die in den Quelltextdateien selbst definiert sind. Um alle Quelltextdateien im Verzeichnis auf einen Schlag zu kompilieren, rufen Sie den Compiler im Verzeichnis der Quelltextdateien auf und verwenden den Platzhalter *: javac *.java
Beachten Sie, dass es in diesem Fall ohne Bedeutung ist, ob das aktuelle Verzeichnis im Klassenpfad steht oder nicht, da die Java-Quelltextdateien aus dem Verzeichnis bei diesem Aufruf alle vorab in den Arbeitsspeicher geladen und geparst werden. 1146
javac – der Compiler
Etwas anders sieht es aus, wenn Sie einzelne Quelltextdateien aus dem Verzeichnis kompilieren möchten, beispielsweise: javac Klasse1.java Klasse2.java
In diesem Fall lädt der Compiler Klasse1.java und Klasse2.java und beginnt dann mit dem Kompilieren von Klasse1.java. Sofern in Klasse1.java nur Typen verwendet werden, die in den vorab geladenen Dateien Klasse1.java und Klasse2.java oder in der Java-Standardbibliothek definiert sind, gibt es keine Probleme. Taucht in Klasse1.java aber eine Klasse auf, die beispielsweise in Hauptklasse.java aus dem aktuellen Verzeichnis definiert ist, findet der Compiler die Definition nur, wenn das aktuelle Verzeichnis im Klassenpfad steht! Notfalls können Sie den Klassenpfad dazu im Aufruf angeben:
43
44
45
javac -classpath . Klasse1.java Klasse2.java
Handelt es sich bei den Dateien im Verzeichnis um die Quelltextdateien eines Programms, genügt es, die Quelltextdatei mit der Hauptklasse (die Klasse, die die main()-Methode enthält) anzugeben und es dem Compiler zu überlassen, die restlichen Quelltextdateien des Programms über den Klassenpfad zusammenzusuchen:
46 47
javac Hauptklasse.java
oder javac -classpath . Hauptklasse.java
Der zugehörige java-Aufruf lautet:
Ausführung
java Hauptklasse
oder java -classpath . Hauptklasse
Wenn Sie verfolgen wollen, welche Quelltextdatei/Klasse wann wie vom Compiler bearbeitet wird, schalten Sie die Option -verbose ein, z. B.: javac -verbose Hauptklasse.java.
TIPP
Szenario 2: Quelltextdateien in verschiedenen Verzeichnissen kompilieren Wenn Sie die Quelltextdateien auf verschiedene Verzeichnisse verteilt haben, müssen Sie dem Compiler den Weg zu den einzelnen Dateien und Verzeichnissen weisen. Angenommen es liegen Ihnen vier Quelltextdateien vor, die auf ein Hauptverzeichnis (im Folgenden durch den . Punkt für das aktuelle Verzeichnis symbolisiert) mit zwei Unterverzeichnissen unter1 und unter2 verteilt sind: ./Hauptklasse.java ./unter1/Klasse1.java ./unter1/Klasse2.java ./unter2/IMeldeDich.java
1147
Die Java-Tools
Wie Sie diese Dateien kompilieren, hängt davon ab, in welcher Beziehung die Dateien zueinander stehen. Programm kompilieren Betrachten wir zuerst den Fall, dass es sich um die Quelltextdateien eines Programms handelt. Hauptklasse sei die Hauptklasse des Programms mit der main()-Methode, in der Objekte der Klassen Klasse1 und Klasse2 erzeugt werden. Klasse1 und Klasse2 implementieren beide die Schnittstelle IMeldeDich. Mit anderen Worten: die Quelltextdateien sind durch die Verwendung der in ihnen definierten Typen in ein Beziehungsgeflecht zusammengebunden, das mit Hauptklasse.java beginnt. Um das Programm zu kompilieren, rufen Sie den Compiler für die Quelltextdatei mit der Hauptklasse auf. Die weiteren Quelltextdateien sucht sich der Compiler über den Klassenpfad zusammen. Für den Fall, dass die Verzeichnisse mit den Quelltextdateien nicht im bestehenden Klassenpfad enthalten sind (was wahrscheinlich ist), geben Sie den zu verwendenden Klassenpfad im Compiler-Aufruf an: javac -classpath .;./unter1;./unter2 Hauptklasse.java
Die erzeugten Class-Dateien werden nach Vorgabe der Quelltextdateien auf die einzelnen Verzeichnisse verteilt. Linux-Anwender müssen die Einträge im Classpath durch Doppelpunkte trennen. HALT
Ausführung
Bei Ausführung des Programms müssen Sie daher ebenfalls den Klassenpfad angeben: java -classpath .;./unter1;./unter2 Hauptklasse
TIPP
Mithilfe der Option -d können Sie den Compiler anweisen, die erzeugten Class-Dateien zusammen in ein spezielles Verzeichnis zu schreiben (beispielsweise in das Hauptverzeichnis des Programms oder in ein Verzeichnis, das Teil des Klassenpfads ist). Der folgende Aufruf speichert die ClassDateien im aktuellen Verzeichnis.5 javac -d . -classpath .;./unter1;./unter2 Hauptklasse.java
Der zugehörige java-Aufruf lautet: java -classpath . Hauptklasse
oder einfach java Hauptklasse
falls das aktuelle Verzeichnis im Klassenpfad eingetragen ist.
5
1148
Bevor Sie den Aufruf ausprobieren, müssen Sie die alten Class-Dateien löschen, da der Compiler sonst keine neuen Class-Dateien generiert.
javac – der Compiler
Bibliothek kompilieren Ganz anders sieht der Compiler-Aufruf aus, wenn die Quelltextdateien voneinander weitgehend unabhängige Klassen enthalten, die Teil einer Klassensammlung oder Bibliothek sind. In diesem Fall müssen alle zu kompilierenden Dateien explizit aufgeführt werden: javac Hauptklasse.java ./unter1/*.java ./unter2/IMeldeDich.java
43
Damit Sie bei umfangreicheren Projekten die einzelnen zu kompilierenden Dateien nicht unnötig wiederholt auflisten müssen, können Sie die Liste auch in einer einfachen Textdatei speichern und diese an den Compiler übergeben.
44
In der Listendatei müssen die einzelnen Quelltextdateien durch Leerzeichen oder Zeilenumbrüche getrennt sein. Für das in diesem Abschnitt verwendete Beispielprojekt könnte die Listendatei beispielsweise wie folgt aufgebaut sein:
45
46
./Hauptklasse.java ./unter1/Klasse1.java ./unter2/Klasse2.java ./unter2/IMeldeDich.java
47
Dem Compiler wird die Listendatei mit einem vorangestellten @-Zeichen übergeben: javac @dateien
Modul kompilieren Schließlich könnte es sein, dass Sie nur einen Teil der Bibliothek oder des Programms kompilieren wollen. Nehmen wir beispielsweise an, Sie hätten die Quelltextdatei Klasse2.java überarbeitet und wollten diese neu kompilieren. Vom Hauptverzeichnis aus rufen Sie den Compiler dazu wie folgt auf: javac ./unter1/Klasse2.java
Meldet der Compiler daraufhin, dass er bestimmte Typen nicht finden kann (»cannot resolve symbol«-Meldungen), führen Sie im Klassenpfad die Verzeichnisse auf, in denen die Class- oder Quelltextdateien mit den benötigten Typen zu finden sind, beispielsweise: javac -classpath .;./unter1;./unter2 .unter1/Klasse2.java
Szenario 3: Bestehende Class-Dateien verwenden Eines der Hauptziele der objektorientierten Programmierung ist es, die Wiederverwendbarkeit bestehenden Codes zu vereinfachen. Voraussetzung dafür, dass Sie in Ihrem Projekt eine Klasse aus einer fremden Class-Datei (etwa aus einer zugekauften Bibliothek oder einem älteren, eigenen Projekt) verwenden können, ist, dass Sie die Class-Datei zusammen mit Ihrem Projekt kompilieren können. Ausgangspunkt sei das Programm aus Szenario 2, das mit dem folgenden Befehl kompiliert wurde: javac -classpath .;./unter1;./unter2 Hauptklasse.java
1149
Die Java-Tools
Class-Datei in System-Klassenpfad eintragen Ist die Class-Datei bzw. die in ihr definierte(n) Klasse(n) von allgemeinem Interesse, sodass es wahrscheinlich erscheint, dass Sie die Klasse(n) noch in weiteren Projekten verwenden werden, lohnt es sich, die Datei in ein Verzeichnis zu kopieren, das im Klassenpfad des Systems (angegeben durch die Umgebungsvariable CLASSPATH) steht – oder das Verzeichnis der Datei in den System-Klassenpfad einzutragen. Angenommen die Datei hieße Helfer.class und stünde in einem Verzeichnis C:/lib, das Teil des System-Klassenpfads ist. Die zugehörige Klasse Helfer wird mindestens in einer Quelltextdatei Ihres Projekts verwendet. Wenn Sie dieses Projekt mit dem oben abgedruckten Compiler-Aufruf kompilieren, erhalten Sie die Fehlermeldung, dass der Compiler für Helfer keine Definition findet. Der Grund liegt auf der Hand: Helfer.class steht zwar im System-Klassenpfad, doch dieser wird nicht ausgewertet, da im CompilerAufruf mittels classpath ein anderer Klassenpfad angegeben wird. Diese unerwünschte Konkurrenz zwischen den Klassenpfadangaben kann auf zwei unterschiedliche Weisen gelöst werden: ■
Sie erweitern die classpath-Angabe um das Verzeichnis der Class-Datei: javac -classpath .;./unter1;./unter2;c:/lib Hauptklasse.java
■
Sie verwenden statt der classpath-Option die Option sourcepath, um dem Compiler den Weg zu den Quelltextdateien des Programms zu weisen: javac -sourcepath .;./unter1;./unter2 Hauptklasse.java
Zur Erinnerung: die in sourcepath aufgeführten Verzeichnisse werden zusätzlich zum gegebenen Klassenpfad (CLASSPATH oder classpath) durchsucht. Der in sourcepath definierte Quellpfad wird allerdings nur nach Quelltextdateien durchsucht. Ausführung
Damit das Programm korrekt und vollständig ausgeführt wird, müssen Sie den erweiterten Klassenpfad angeben: java -classpath .;./unter1;./unter2;c:/lib Hauptklasse
Java-Bibliotheken werden häufig in Form von JAR-Archiven ausgeliefert. In diesem Fall tragen Sie einfach das JAR-Archiv in den Klassenpfad ein. TIPP
Class-Datei an Ursprungsort belassen Ist die Class-Datei von nur geringem allgemeinen Interesse oder gibt es sonstige Gründe dafür, die Datei nicht in den System-Klassenpfad aufzunehmen, können Sie die Datei am Ursprungsort belassen. Den Weg zur Datei müssen Sie dann über die classpath-Option angeben: javac -classpath .;./unter1;./unter2;c:/lib Hauptklasse.java
Class-Datei in Projektverzeichnis kopieren Wenn Sie die Class-Datei zentral im System-Klassenpfad speichern oder an ihrem Ursprungsport belassen, besteht die Gefahr, dass die Class-Datei 1150
javac – der Compiler
irgendwann aktualisiert wird und die vorgenommenen Änderungen zu den Projekten, in denen Sie die Class-Datei wieder verwendet haben, nicht kompatibel sind. Für Class-Dateien aus größeren, zugekauften Bibliotheken, zu denen Ihnen keine Quelltextdateien vorliegen, ist diese Gefahr – zumindest soweit es das Damokles-Schwert einer unbemerkten Aktualisierung angeht – eher gering. Wenn Sie jedoch Class-Dateien aus eigenen Projekten wiederverwenden, steigen die Chancen, dass Sie (oder Mitglieder Ihres Projektteams) später Änderungen vornehmen, ohne an die Konsequenzen für die abhängigen Projekte zu denken. In solchen Fällen hilft ein ordentliches Team- und Versionsmanagement und – soweit opportun – das Kopieren der Class-Dateien in die abhängigen Projekte.
43
44
Der nachfolgende Aufruf geht davon aus, dass die verwendeten ClassDateien in ein eigenes Unterverzeichnis /lib kopiert wurden:
45
javac -classpath .;./unter1;./unter2;./lib Hauptklasse.java
46
Bezüglich der versehentlichen Neukompilierung oder Ersetzung von wieder verwendeten Class-Dateien gibt es noch einen weiteren Punkt zu beachten. Wenn Sie ein Projekt kompilieren, das Klassen aus Class-Dateien von Bibliotheken oder anderen Projekten verwendet, und der Compiler im Klassen- oder Quellpfad neben der Class-Datei auch die zugehörige Quelltextdatei findet, kompiliert er die Class-Datei neu, wenn die Quelltextdatei neueren Datums ist als die Class-Datei!
47
HALT
Szenario 4: Aktualisierung versus Neukompilation In den bisherigen Beispielen wurde fast ausschließlich die Option classpath benutzt, um dem Compiler anzuzeigen, wo er Quelldateien (Quelltextebenso wie Class-Dateien) mit eventuell benötigten Typdefinitionen finden kann. Den Weg zu den Quelltextdateien könnten Sie aber genauso gut über die Option sourcepath angeben. Sie können diese Option beispielsweise zur Erzwingung einer vollständigen Neukompilation verwenden. Bleiben wir bei dem Beispielprogramm aus Szenario 2 und 3 mit dem Aufbau: ./Hauptklasse.java ./unter1/Klasse1.java ./unter2/Klasse2.java ./unter2/IMeldeDich.java
Wenn Sie das Programm erstmalig wie folgt kompilieren: javac -classpath .;./unter1;./unter2 Hauptklasse.java
stehen danach in den einzelnen Projektverzeichnissen neben den Quelltextdateien auch die zugehörigen Class-Dateien: ./ Hauptklasse.java Hauptklasse.class
./unter1 Klasse1.java Klasse1.class Klasse2.java Klasse2.class
./unter2 IMeldeDich.java IMeldeDich.class
1151
Die Java-Tools
Wenn Sie nun in IMeldeDich.java eine Änderung vornehmen, und das Programm mit dem alten Aufrufe javac -classpath .;./unter1;./unter2 Hauptklasse.java
neu kompilieren, werden Sie zu Ihrem Bedauern feststellen müssen, dass IMeldeDich.class nicht neu erzeugt wurde. Der Grund ist einfach: Der Compiler kompiliert Hauptklasse.java. In Hauptklasse werden die beiden Klassen Klasse1 und Klasse2 verwendet. Der Compiler sucht also nach Definitionen dieser Klassen und findet zu seiner Freude bereits fertige ClassDateien. Damit ist seine Arbeit an diesem Punkt beendet. Dass die Klassen Klasse1 und Klasse2 die Schnittstelle IMeldeDich implementieren, weiß er nicht. Er muss es auch nicht wissen, denn alles was er zum Erstellen von Hauptklasse.class benötigt, ist der Bytecode von Klasse1 und Klasse2, und der liegt ihm ja vor. Leider werden dadurch Ihre Änderungen in IMeldeDich ignoriert, das erstellte Programm ist nicht mehr aktuell. Sie könnten den Compiler explizit zur Kompilierung von IMeldeDich.java aufrufen, Sie können aber auch einfach statt –classpath die Option –sourcepath verwenden: javac -sourcepath .;./unter1;./unter2 Hauptklasse.java
Jetzt findet der Compiler die bereits erzeugten Class-Dateien nicht mehr (im Quellpfad – wo die erzeugten Class-Dateien stehen – sucht er nur nach .java-Dateien und im System-Klassenpfad, der jetzt wieder gilt, sind die Dateien nicht zu finden). Da ihm nur die Quelltextdateien vorliegen, muss er das Projekt vollständig neu erstellen. Ausführung
Wie gehabt mit java -classpath .;./unter1;./unter2 Hauptklasse
Klassenpfad oder Quellpfad EXKURS
Den Weg zu den Quelltextdateien können Sie sowohl über die Option classpath als auch die Option sourcepath anzeigen. Für sourcepath spricht außer der Möglichkeit der oben angesprochenen vollständigen Neukompilation, dass der System-Klassenpfad (Umgebungsvariable CLASSPATH) gültig bleibt. Außerdem können Sie mit sourcepath die Lesbarkeit Ihrer Compiler-Aufrufe verbessern, indem Sie mit sourcepath den Weg zu den Quelltextdateien und mit classpath den Weg zu den verwendeten Class-Dateien angeben, beispielsweise: javac -sourcepath .;./unter1;./unter2 -classpath ./lib Hauptklasse.java
Programmierern, die ihre Compiler-Aufrufe direkt auf der Konsole eintippen, wird die aussagekräftigere Syntax wohl mit zu viel Tipparbeit verbunden sein. Wenn Sie aber Batch-Dateien zum Abspeichern und Automatisieren der Compiler-Aufrufe nutzen (siehe Szenario 7), hält sich der Aufwand in Grenzen, und wenn Sie nach einer längeren Pause zu dem Projekt zurückkehren,
1152
javac – der Compiler
sind sie womöglich froh, sich mit einem Blick in die Batch-Datei über die Verteilung der Quelldateien informieren zu können. Letztlich hängt es vom Aufbau des zu kompilierenden Projekts und den Gewohnheiten und Vorlieben des Programmierers ab, ob classpath, sourcepath oder eine Kombination beider Optionen verwendet wird.
43
Szenario 5: Quelltextdateien eines Pakets kompilieren Der Interpreter erwartet, dass die Class-Dateien eines Pakets in einem gleichnamigen Unterverzeichnis zu finden sind. Die Class-Datei einer Klasse, die in einem Paket demo definiert wurde, erwartet er folglich in einem Verzeichnis demo vorzufinden. Wurde die Klasse in einem Unterpaket definiert (beispielsweise eigenesPaket.unterPaket) erwartet der Interpreter, dass die Class-Datei in einem entsprechenden Verzeichnispfad steht (/eigenesPaket/unterPaket).
44
45
46
Um diesem Umstand Rechnung zu tragen, gibt es zwei Möglichkeiten: ■
■
Sie speichern bereits die Quelltextdateien in Unterverzeichnissen, die die Pakethierarchie nachbilden, und lassen den Compiler wie bisher die Class-Dateien bei den zugehörigen Quelltextdateien ablegen. Sie verwahren die Quelltextdateien unabhängig von den Class-Dateien und geben dem Compiler explizit mit der Option –d an, wo die ClassDateien abzulegen sind.
47
Wenn Ihr Programm ein eigenes Paket demo definiert und alle Quelltextdateien in einem Verzeichnis stehen, wechseln Sie auf der Konsole in dieses Verzeichnis und kompilieren Sie die Quelltextdateien mit dem Befehl: javac –d . *.java
Um das Programm auszuführen, rufen Sie folgenden Befehl auf:
Ausführung
java demo.Hauptklasse (bzw. java –classpath . demo.Hauptklasse)
Wenn Sie die Quelltextdateien wie in den bisherigen Szenarien auf verschiedene Verzeichnisse verteilt haben, listen Sie dem Compiler explizit auf, welche Quelltextdateien er laden und kompilieren soll: javac -d . *.java ./unter1/*.java ./unter2/*.java
Der java-Aufruf bleibt unverändert:
Ausführung
java demo.Hauptklasse (bzw. java –classpath . demo.Hauptklasse)
Szenario 6: Quelltextdateien mehrerer Pakete kompilieren Spätestens wenn Sie Programme kompilieren, die Klassen enthalten, die in verschiedenen Paketen definiert sind, sollten Sie dazu übergehen, für jedes Paket unter dem Programmhauptverzeichnis ein eigenes Unterverzeichnis anzulegen und auf diese die Quelltextdateien zu verteilen.
1153
Die Java-Tools
Für das folgende Beispiel seien die Ihnen mittlerweile wohlbekannten Beispielklassen, wie in der Tabelle aufgeführt, auf Pakete und Verzeichnisse verteilt. Tabelle 44.2: Aufteilung der Klassen
Klasse
Paket
Verzeichnis
Hauptklasse
unnamed (keine package-Deklaration)
Sz6 (Hauptverzeichnis des Programms)
Klasse1
klassen.unter1
Sz6/klassen/unter1
Klasse2
klassen.unter2
Sz6/klassen/unter2
IMeldeDich
schnittstellen
Sz6/schnittstellen
Bei korrekter Verteilung der Quelltextdateien auf die Unterverzeichnisse können Sie das gesamte Projekt danach mit folgendem Befehl kompilieren: javac Hauptklasse.java
oder javac -classpath . Hauptklasse.java (Wenn aktuelles Verzeichnis nicht in System-Klassenpfad)
Ausführung
java Hauptklasse
oder java -classpath . Hauptklasse (Wenn aktuelles Verzeichnis nicht in System-Klassenpfad)
Szenario 7: Class-Dateien einer älteren Java-Plattform verwenden Nicht immer ist es wünschenswert, mit dem neuesten JDK und der neuesten Ausgabe der Java-Standardbibliothek zu arbeiten. Wenn Sie beispielsweise Applets für Webseiten schreiben, müssen Sie davon ausgehen, dass die Websurfer, die Ihre Applets herunterladen, nicht immer das neueste Java-Plug-In in ihren Browsern installiert haben. Sie können dem Rechnung tragen, indem Sie die entsprechenden Websurfer zur Sun-Website verweisen, wo sie das aktuelle Plug-In herunterladen können. Sie können aber auch Ihr Applet gegen eine ältere JRE-Version kompilieren, sodass es von älteren (und neueren6) Plug-Ins problemlos ausgeführt werden kann. Um ein Java-Programm für eine ältere JRE-Version zu kompilieren, gehen Sie wie folgt vor: 1. 2.
6
1154
Laden Sie von der Sun-Website eine ältere JDK-Version herunter. Installieren Sie diese parallel, sodass die Bibliotheksklassen (Ordner \lib) verfügbar sind.
Die Abwärtskompatibilität ist zumeist sichergestellt.
java – der Interpreter
3. Beim Compileraufruf geben Sie dann die gewünschte Java-Version an und den Pfad zu den zu verwendenden Bibliotheksklassen: javac -target 1.1 -bootclasspath jdk1.1.8\lib\classes.zip -extdirs "" *.java
4. Hier werden die Quelldateien (*.java) für die 1.1-Version der Java Virtual Machine kompiliert, die zugehörigen Bibliotheksdateien stehen in jdk1.1.8\lib\classes.zip.
43
Szenario 8: Aufrufe automatisieren
44
Um komplexe Aufrufe zu vereinfachen, können Sie ■ ■ ■
den Platzhalter * verwenden, Listendateien aufsetzen (siehe Szenario 2), Batchdateien schreiben (Shell-Scripte unter Linux).
45
Unter Windows legen Sie Batchdateien wie folgt an (unter Linux ist die Vorgehensweise analog):
46
1.
47
2.
Öffnen Sie den Notepad-Editor (START/AUSFÜHREN, in Dialogfenster notepad eingeben und abschicken). Tippen Sie die Befehle zum Kompilieren und Ausführen des Programms ein, beispielsweise: javac -classpath . Hauptklasse.java java -classpath . Hauptklasse
3. Speichern Sie die Batchdatei in dem Hauptverzeichnis des Programms mit der Extension .bat. 4. Rufen Sie die Batchdatei von der Konsole aus auf. Abbildung 44.1: Ausführung der Batchdatei von der Windows-Konsole
44.2 java – der Interpreter Um ein Java-Programm auszuführen (Konsolen- und GUI-Anwendungen, keine Applets), übergeben Sie die Class-Datei, die die main()-Methode enthält, an den java-Interpreter: java Optionen Classdatei [Argumente]
1155
Die Java-Tools
oder javaw Optionen Classdatei [Argumente]
Wenn Sie das Programm in ein JAR-Archiv gepackt haben (siehe unten), übergeben Sie die JAR-Datei zusammen mit der Option –jar: java Optionen –jar Jardatei [Argumente] javaw Optionen –jar Jardatei [Argumente]
TIPP
javaw und java unterscheiden sich darin, dass zu java ein Konsolenfenster geöffnet wird, während für Programme, die mit javaw gestartet werden, kein Konsolenfenster erscheint. javaw wird daher üblicherweise zum Starten von GUI-Anwendungen verwendet. Aufrufe Wie Sie Java-Anwendungen durch Aufruf des Java-Interpreters java ausführen, wurden bereits in den Aufruf-Szenarien zum javac-Compiler gezeigt. An dieser Stelle möchten wir daher nur noch die Beschreibung der einzelnen Optionen nachliefern. Optionen
Tabelle 44.3: Die wichtigsten InterpreterOptionen
Option
Beschreibung
-classpath pfad -cp pfad
Gibt den Klassenpfad an, in dem der Interpreter nach Typdefinitionen sucht. Die im Klassenpfad aufgeführten Verzeichnisse und Archive (JAR und ZIP) werden durch Semikolons (Doppelpunkt unter Linux) getrennt. Der Punkt ».« steht für das aktuelle Verzeichnis. Wenn Sie die classpath-Option setzen, wird der in der CLASSPATH-Umgebungsvariablen angegebene Klassenpfad ignoriert. (Wenn Sie weder die Option noch die Umgebungsvariable gesetzt haben, besteht der Klassenpfad aus dem aktuellen Verzeichnis.)
-Dname=Wert
Setzt eine Umgebungsvariable.
-jar
Zur Ausführung von JAR-Dateien. Die angegebene JAR-Datei muss in Ihrem Manifest einen Main-Class:-Eintrag haben, der die Class-Datei mit der main()-Methode angibt.
-verbose -verbose:class
Informiert über Klassen, die geladen werden. Die Meldungen erscheinen auf der Konsole. Zusätzlich können Sie sich über die Arbeit der Speicherbereinigung (-verbose:gc) und den Aufruf nativer Methoden (-verbose:jni) informieren lassen.
1156
-version
Zeigt die Versionsnummer des Interpreters an.
-help -?
Listet die Standardoptionen auf.
-X
Informiert über Nicht-Standardoptionen.
jar – Archive erstellen
Ein Punkt, der viel Ärger und Verwirrung stiftet, ist, dass der Interpreter das aktuelle Verzeichnis nur dann durchsucht, wenn es Teil des Klassenpfads ist. Das Verzeichnis muss dazu – symbolisiert durch den Punkt .– explizit im Klassenpfad eingetragen sein (beispielsweise CLASSPATH=.;c:\bin). Wenn keine CLASSPATH-Umgebungsvariable definiert ist, wird das aktuelle Verzeichnis ebenfalls durchsucht – es fungiert dann quasi als Standardklassenpfad.
TIPP
43
Denken Sie in diesem Zusammenhang auch daran, dass die CLASSPATHUmgebungsvariable nicht nur von Ihnen, sondern auch von Installationsprogrammen gesetzt und verändert werden kann. Sollten Sie beispielsweise feststellen, dass sich alte Programme, deren Quelldateien zusammen in einem Verzeichnis stehen, urplötzlich nicht mehr kompilieren lassen, könnte dies daran liegen, dass auf Ihrem System früher keine CLASSPATH-Variable definiert war, Sie zwischenzeitlich aber ein Programm installiert haben, welches die Umgebungsvariable ohne Eintrag für das aktuelle Verzeichnis gesetzt hat.
44
45
46 47
44.3 jar – Archive erstellen Mit dem jar-Tool können Sie die Dateien eines Programms (Class-Dateien, Bilddateien, Sounddateien u. a.) zusammen in ein Archiv packen und ausliefern. Der Kunde braucht das Archiv nicht zu entpacken, um das darin enthaltene Programm auszuführen oder die im Archiv abgelegten Bibliotheksklassen zu verwenden. Programme aus Archiven können direkt vom Interpreter ausgeführt werden (Option –jar), sofern das Archiv eine Manifest-Datei enthält, die auf die Class-Datei mit der main()-Methode verweist. Klassen aus Archiven können von anderen Programmen genutzt werden, wenn das Archiv im Klassenpfad steht.
TIPP
Es gibt verschiedene Gründe, die für die Erstellung eines Archivs sprechen: ■ ■
■
Die Dateien werden übersichtlich und sicher verwahrt. Für Anwendungen, die aus dem Internet heruntergeladen werden (insbesondere Applets) werden die Download-Zeiten durch die Komprimierung und die Übertragung einer einzelnen Datei dramatisch reduziert. Die Dateien in einem Archiv können signiert werden (siehe Kapitel 40.3).
Aufrufe Die allgemeine Syntax für den Aufruf von jar lautet: jar Optionen Zieldatei [Manifestdatei|MainKlasse] Eingabedateien
Die optionalen Argumente Manifestdatei und MainKlasse dienen dazu, die Klasse im JAR-Archiv anzugeben, welche die main()-Methode enthält. Dies ist notwendig, damit der java-Interpreter später bei Ausführung des JAR1157
Die Java-Tools
Archivs weiß, wo die Programmausführung beginnt. Zwei Möglichkeiten gibt es, die Klasse mit der main()-Methode anzuzeigen: ■
Vor Java 6 mussten Sie eine Manifest-Datei (Textdatei mit der Endung .mf) und einem Main-Class-Eintrag erstellen – beispielsweise Main-Class: Hauptklasse
Neu in Java 6
■
wobei »Hauptklasse« hier für die Klasse steht, in der main() definiert ist. Anschließend rufen Sie jar mit der Option m und der Manifest-Datei auf (siehe die unten nachfolgende Aufruf-Beispiele). (Alternativ können Sie auch zuerst das JAR-Archiv erstellen, die automatisch erstellte Manifest-Datei entpacken, die Zeile mit der Hauptklasse einfügen und dann das Archiv neu erstellen.) Seit Java 6 geht es auch etwas einfacher. Sie setzen die Option e und übergeben den Namen der Hauptklasse als Argument an jar. jar cfe archiv.jar Hauptklasse Hauptklasse.class Hilfsklasse.class
TIPP
Applets benötigen keinen Main-Class-Eintrag. Der Name der Applet-Klasse, deren Methoden der Browser zur Ausführung des Applets aufruft, wird im HTML-Code der Webseite angegeben. Typische Aufrufe sind:
Tabelle 44.4: Typische jar-Aufrufe
Aufruf
Beschreibung
jar cf archiv.jar k1.class k2.class
Erzeugt ein neues Archiv namens archiv.jar und fügt diesem die Dateien k1.class und k2.class hinzu. Zusätzlich erzeugt jar eine passende Manifest-Datei Manifest.mf, allerdings ohne Main-Class-Eintrag.
jar cf archiv.jar *.*
Erzeugt ein neues Archiv und fügt alle Dateien im aktuellen Verzeichnis (inklusive Unterverzeichnisse) hinzu.
jar cf archiv.jar images sound *.class
Erzeugt ein neues Archiv und fügt alle Class-Dateien im aktuellen Verzeichnis und die Unterverzeichnisse images und sound hinzu.
jar cfm archiv.jar manifest.mf *.*
Erzeugt ein neues Archiv und fügt alle Dateien im aktuellen Verzeichnis (inklusive Unterverzeichnisse) hinzu. Verwendet die angegebene Manifest-Datei.
jar cfe archiv.jar Hauptklasse *.*
Erzeugt ein neues Archiv und fügt alle Dateien im aktuellen Verzeichnis (inklusive Unterverzeichnisse) hinzu. Registriert Hauptklasse als Main-Class. (Hauptklasse.class muss selbstredend unter den in das Archiv aufgenommenen Class-Dateien sein.)
1158
jar tf archiv.jar
Gibt das Inhaltsverzeichnis des angegebenen Archivs aus. (Sie können JAR-Archive auch zum Einsehen in Winzip laden.)
jar xf archiv.jar
Entpackt das JAR-Archiv.
jar – Archive erstellen
Automatisch erzeugte Manifest-Dateien enthalten natürlich keine Hinweise auf eventuell vorhandene main()-Methoden. Wenn Sie also eine Anwendung in ein JAR-Archiv packen, müssen Sie die Manifestdatei entweder nachbearbeiten und eine Zeile: Main-class: Hauptklasse
hinzufügen (wobei »Hauptklasse«, hier für die Klasse steht, in der main() definiert ist). Oder Sie erstellen zuerst eine Manifest-Datei und übergeben diese jar (Option m).
43
44
Applets benötigen keinen vergleichbaren Eintrag. Der Name der AppletKlasse, deren Methoden der Browser zur Ausführung des Applets aufruft, wird im HTML-Code der Webseite angegeben.
45
Optionen
46
Die jar-Optionen bestehen aus einzelnen Buchstaben ohne Bindestrich, die mehr oder weniger beliebig kombiniert werden können. Beachten Sie aber, dass bei Optionen, die weitere Argumente erfordern (f, m ...), die Reihenfolge der Optionen auch die Reihenfolge der Argumente vorgibt. Option
Beschreibung
c
Erzeugt ein neues Archiv.
47 Tabelle 44.5: Wichtige jar-Optionen
(c steht für create.)
f
Name des zu bearbeitenden Archivs. (f steht für file.)
e
Name der Klasse mit der main()-Methode. (e steht für executable.)
i
Erzeugt Indexinformationen für ein Archiv. (Beschleunigt das Laden der Klassen.) (i steht für index.)
-Joption
Übergibt die angegebene java-Option an den Interpreter.
m
Verwendet die angegebene Manifest-Datei. (m steht für manifest.)
M
Es wird keine Manifest-Datei erzeugt.
O
Die Dateien werden nicht komprimiert.
t
Gibt den Inhalt des angegebenen Archivs auf die Konsole aus. (t steht für table.)
u
Fügt dem angegebenen, bestehenden Archiv weitere Dateien hinzu. (u steht für update.)
v
Erzeugt ausführliche Statusmeldungen. (v steht für verbose.)
1159
Die Java-Tools
44.4 javadoc – Dokumentationen erstellen Mit javadoc können Sie die Klassen Ihrer Programme und Bibliotheken im Stile der offiziellen Java-API-Referenz dokumentieren. Sie müssen lediglich entsprechend formatierte Kommentare in Ihre Quelltexte einfügen und dann javadoc aufrufen. javadoc-Kommentare gleichen den üblichen mehrzeiligen Kommentaren, beginnen aber mit zwei Sternchen hinter dem Slash: /** * Dies ist ein Dokumentationskommentar. */
Es ist üblich, die einzelnen Zeilen eines Dokumentationskommentars mit einem Sternchen zu beginnen, notwendig ist dies aber nicht. TIPP
Mit diesen Kommentaren können Sie Klassen, Schnittstellen, Konstruktoren, Methoden und Felder dokumentieren. Stellen Sie den Kommentar dazu einfach direkt vor die Definition des Elements: /** * Dokumentation zu EineKlasse */ public class EineKlasse { /** * Dokumentation des Feldes einFeld */ public int einFeld; /** * Dokumentation der Methode main() * */ public static void main(String[] args) { ...
Der Dokumentartext sollte aus zwei Teilen bestehen: ■ ■
einer Kurzbeschreibung, die aus einem Satz besteht, und einer nachfolgenden ausführlichen Beschreibung.
Dokumentartexte enden mit dem abschließenden */ oder wenn innerhalb des Kommentars ein Tag auftaucht. Tags sind Marker, die alle mit @ beginnen und spezielle Informationen kennzeichnen, die von javadoc gesondert formatiert werden – beispielsweise die Parameter einer Methode.
1160
jdb – der Debugger
Tag-Marker
Beschreibung
@author
Angabe des Autors
Tabelle 44.6: Die javadoc-Tags
@author name @deprecated
Zeigt an, dass das Element nicht mehr verwendet werden sollte.
@deprecated Hinweistext @exception @throws
43
Angabe der Exception-Klasse, die von der Methode ausgelöst oder weitergeleitet werden kann.
@throws Exceptionklassenname Beschreibung @param
44
Beschreibung eines Parameters
@param Name Beschreibung @return
45
Beschreibung des Rückgabewerts
@return Beschreibung @see
46
Verweis auf andere Textstelle
@see Referenza @serial @serialData @serialField
Zur Kennzeichnung serialisierter Elemente
@since
Gibt an, seit wann dieses Element existiert.
@serial
@since @version
47
JDK1.0
Angabe der Versionsnummer
@version 1.73, 12/03/01 a.
»Referenz« kann beispielsweise der Name einer Methode oder Klasse sein, aber auch ein HTML-Tag: Methode01.
Zur Erzeugung der HTML-Dokumentation rufen Sie javadoc von der Konsole aus dem Verzeichnis der Quelldateien auf – beispielsweise: javadoc *.java
44.5 jdb – der Debugger Während syntaktische Fehler bereits bei der Kompilation vom Compiler abgefangen und ihre Fehlerquelle meist auch korrekt identifiziert wird, steht der Programmierer bei Fehlern, die zur Laufzeit auftreten, meist vor dem Problem, dass er die Codezeile, die für den Fehler verantwortlich ist, selbst ausfindig machen muss. Dass die Stelle im Programm, an der der Fehler auftritt, dabei nicht notwendigerweise mit der Fehler verursachenden Stelle übereinstimmt, erleichtert nicht gerade die Fehlerlokalisation. Manchmal deckt schon ein gründliches Durchlesen der in Frage kommenden Codestellen den Fehler auf. Manchmal hilft jedoch auch dies nicht weiter, und der Programmierer muss prüfen, ob die Methoden des Programms
1161
Die Java-Tools
in korrekter Reihenfolge ausgeführt werden und wie sich die Werte seiner Variablen während der Ausführung des Programms verändern. Er kann dazu System.err.println()-Anweisungen in das Programm einfügen oder einen Debugger verwenden. Ein Debugger ist eine Art Super-Programm, das andere Programme ausführen und dabei überwachen kann. Eine Fehleranalyse führt der Debugger selbst aber nicht durch – dies ist Ihre Aufgabe. Der Debugger hilft Ihnen lediglich dabei, zur Laufzeit gezielt Informationen über die Ausführung des Programms zu sammeln. Grundsätzlich gehen Sie beim Debuggen folgendermaßen vor: 1. 2.
Sie laden das Programm in den Debugger. Sie definieren Haltepunkte, d.h., Sie teilen dem Debugger mit, dass die Ausführung des Programms bei Erreichen bestimmter Quelltextzeilen angehalten werden soll. 3. Sie führen das Programm von Haltepunkt zu Haltepunkt oder schrittweise mit speziellen Debuggerbefehlen aus und kontrollieren dabei, ob der Programmfluss korrekt ist (ob beispielsweise in einer if-Bedingung korrekt verzweigt wird, ob eine Schleife ausgeführt oder eine Methode aufgerufen wird). 4. Wurde die Programmausführung vom Debugger angehalten, können Sie sich vom Debugger die Inhalte der Variablen des Programms anzeigen lassen. Auf diese Weise können Sie beispielsweise die Ausführung von Berechnungen oder die Inkrementierung von Schleifenvariablen kontrollieren. Der JDK-Debugger
Der JDK-Debugger heißt jdb und eignet sich zur Fehlersuche in Anwendungen und Applets. Allerdings handelt es sich um ein recht einfaches Programm. Wesentlich komfortabler ist der Einsatz von Debuggern aus integrierten Entwicklungsumgebungen (beispielsweise JBuilder). Vorbereitungen Um ein Programm mit dem jdb zu debuggen, muss zunächst der javacCompiler spezielle Debug-Informationen hinzufügen, die der jdb-Debugger benötigt. Dazu geben Sie beim Kompilieren mit javac die Option -g an: javac -g Fehler.java
Debug-Sitzung starten Nun kann das Programm im Debugger gestartet werden: jdb Fehler
Falls ein Applet debuggt werden soll, muss der Appletviewer mit der Option -debug aufgerufen werden. Er sorgt dann dafür, dass der jdb mit aufgerufen wird. Nach dem Laden und Initialisieren wartet der jdb auf Ihre Befehle.
1162
Weitere Tools
Wichtige jdb-Kommandos: Kommando
Beschreibung
run arg1 arg2
Startet die Ausführung des Programms; falls das Programm Parameter erwartet, können sie mit angegeben werden.
stop at Klasse:Zeile
Setzt einen Haltepunkt in Klasse Klasse in Zeile Zeile.
stop in Klasse.Methode
Setzt einen Haltepunkt in der Methode Methode von Klasse Klasse. Gestoppt wird bei der ersten Anweisung.
step
Eine Codezeile ausführen.
cont
Programmausführung fortsetzen (nach einem Haltepunkt).
list
Den Quelltext anzeigen.
locals
Anzeigen der lokalen Variablen.
print Name
Anzeigen der Variable Name.
where
Die Abfolge der Methodenaufrufe zeigen.
quit
jdb beenden.
help
Übersicht über alle jdb-Befehle ausgeben.
!!
Letztes Kommando wiederholen.
Tabelle 44.7: jdb-Befehle
43
44
45
46 47
44.6 Weitere Tools Zum JDK gehören noch eine Reihe weiterer Tools, deren Verwendung zum Teil in den entsprechenden Kapiteln des Buches beschrieben ist: Tool
Kapitel
appletviewer
38.1.4
htmlconverter
38.1.5
jarsigner
40.3
keytool
40.3
policytool
40.3
rmic
37.3
rmiregistry
37.2, 37.3 und 37.5
wsgen
43.3
wsimport
43.4
Tabelle 44.8: Weitere Tools
1163
Inhalt
45 Installation von MySQL 43 44
Auf der Buch-CD finden Sie im Verzeichnis Software\MySQL die Installationsdatei mysql-5.0.27-win32.zip für Windows-Systeme bzw. MySQL-server5.0.27-0.i386.rpm für Linux (x86). Wir beschreiben im Nachfolgenden die Installation für Windows; signifikante Abweichungen für Linux werden angegeben.
45 46
Die folgende Beschreibung dient lediglich dazu, eine lauffähige Grundinstallation von MySQL zu erstellen, sodass die Beispielprogramme des Buches nachvollzogen werden können. Dies ist keine Anleitung für eine Installation auf Produktionsniveau!
47
INFO
45.1 Der MySQL-Server Gehen Sie folgendermaßen vor: 1. 2.
1165
Index
Loggen Sie sich als ein Benutzer mit Administrationsrechten ein. Unter Windows entpacken Sie das Installationsarchiv von der CD in ein temporäres Verzeichnis; als Linux-Anwender kopieren Sie die RPMDatei von der CD in ein temporäres Verzeichnis. 3. Öffnen Sie eine Konsole, wechseln Sie in das temporäre Verzeichnis und führen Sie unter Windows das Programm setup.exe aus. Wählen Sie als Installationsart TYPICAL, um MySQL in das Verzeichnis C:\Programme\ MySQL\MySQL Server 5.0 bzw. C:\ProgramFiles\MySQL\MySQL Server 5.0 zu installieren (oder wählen Sie CUSTOM, wenn Sie die Installation anpassen bzw. ein anderes Installationsverzeichnis verwenden möchten). Unter Linux geben Sie zur Installation das Kommando rpm -i --force MySQL-server-4.0.21-0.i386.rpm ein, was eine Installation nach /usr/bin bewirkt. Eine vorhandene MySQL-Installation wird dabei überschrieben.
Installation von MySQL
TIPP
Unter Windows erscheint am Ende der Installation von Version 5.0 ein Dialog zur Anmeldung bei MySQL.com, den Sie getrost durch Auswahl der Option SKIP SIGN-UP überspringen können. Anschließend wird der Konfigurationsassistent gestartet, den Sie dazu nutzen können, ein root-Passwort festzulegen. Führen Sie den Assistenten aus, übernehmen oder bearbeiten Sie die Voreinstellungen, bis Sie zur Option Modify Security Settings gelangen, wo Sie das Passwort eingeben. (Sie können das Passwort aber auch wie unten beschrieben festlegen.) MySQL starten Unter Windows müssen Sie diesen Schritt nur ausführen, wenn Sie MySQL nicht als automatisch gestarteten Dienst installiert haben (Einstellung im Konfigurationsassistenten). 4. Öffnen Sie ein Konsolenfenster und wechseln Sie in das bin-Verzeichnis der MySQL-Installation. Starten Sie den MySQL-Server mit dem Aufruf: mysqld --console bzw. ./mysqld_safe
(unter Windows) (unter Linux)
Abbildung 45.1: Erstmaliger Start des MySQL-Serverprogramms
Root-Passwort ändern Unter Windows müssen Sie diesen Schritt nur ausführen, wenn Sie das Passwort nicht schon im Konfigurationsassistenten eingerichtet haben. 5. Öffnen Sie ein weiteres Konsolenfenster und wechseln Sie in das MySQL-Installationsverzeichnis. Starten Sie den MySQL-Client durch Eingabe des Kommandos: mysql –u root mysql
Sie sind nun als »MySQL«-Root (= Administrator) eingeloggt. Setzen Sie jetzt sicherheitshalber ein Passwort, z. B. »frolle«1: SET password for root@localhost=PASSWORD('frolle');
1
1166
Groß- und Kleinschreibung spielt bei den nachfolgenden MySQL-Kommandos übrigens keine Rolle!
Der JDBC-Treiber
In Zukunft müssen Sie nun beim Einloggen noch den Parameter –p mitgeben, damit das Root-Passwort abgefragt wird: TIPP
mysql –u root -p mysql
Benutzer anlegen
43
6. Als Nächstes legen Sie den Benutzer »javauser« mit dem Passwort »lomu« an: GRANT ALL PRIVILEGES ON *.* TO javauser@localhost IDENTIFIED BY 'lomu' WITH GRANT OPTION;
7.
44
Falls Sie sich auch von einem anderen Rechner zum MySQL-Server verbinden wollen, muss ein weiterer GRANT-Befehl wie oben eingegeben werden, wobei localhost durch den entsprechenden Rechnernamen oder seine IP-Adresse zu ersetzen ist. Verlassen Sie mysql nun durch Eingabe von quit;.
45
46
Wichtige MySQL-Kommandos:
47
Beschreibung
Befehl
Einloggen als lokaler Benutzer mit Passwort
mysql –u username –p;
Einloggen als 'javauser' von anderem Rechner aus
mysql --host serverName –u javauser –p;
Ausloggen aus dem MySQL-Client
quit;
Beenden des MySQL-Servers
mysqladmin -u root -p shutdown;
Tabelle 45.1: Ausgesuchte MySQL-Befehle
45.2 Der JDBC-Treiber Kopieren Sie von der Buch-CD aus dem Verzeichnis Software\MySQL\Treiber das JAR-Archiv mysql-connector-java-5.0.4-bin.jar in ein beliebiges Verzeichnis (beispielsweise in ein Verzeichis lib, das Sie unter dem MySQLInstallationsverzeichnis anlegen) und erweitern Sie die CLASSPATH-Umgebungsvariable um den vollen Dateinamen, z. B. CLASSPATH=.;C:\Programme\MySQL\MySQL Server 5.0\lib\mysql-connector-java5.0.4-bin.jar
1167
Inhalt
46 Zahlensysteme 43 44
Integer-Zahlen werden vom Compiler gemäß dem 2n-Komplement in Bitfolgen umgewandelt. Für positive Zahlen entspricht diese Umwandlung der einfachen Umrechnung ins Binärsystem (auch Dualsystem genannt), mit führender 0 für das Vorzeichen. (Siehe Kapitel 5.5, Abschnitt »IntegerTypen« für ausführlichere Informationen zum 2n-Komplement.) Auch Mantisse und Exponent von Gleitkommazahlen oder die Zeichencodes von charWerten werden als Binärzahlen abgespeichert.
45
46
Ab und an ist es von Vorteil, diese Werte auf Bitebene zu manipulieren. Die Grundvoraussetzung dafür ist, dass man weiß, wie Werte aus dem Binärsystem ins Dezimalsystem und umgekehrt umgerechnet werden können.
47
46.1 Umrechnungen Um eine natürliche Dezimalzahl in eine Binärzahl umzurechnen, dividieren Sie fortwährend durch 2 und notieren die Reste der Teildivision in umgekehrter Reihenfolge: 97 : 2 -------48 | 1 24 | 0 12 | 0 6 | 0 3 | 0 1 | 1 0 | 1
= 1100001
Um eine Binärzahl in eine Dezimalzahl umzurechnen, multiplizieren Sie die Ziffern von rechts nach links mit zunehmenden Potenzen von 2 (beginnend mit 20). 2^7 | 2^6 | 2^5 | 2^4 | 2^3 | 2^2 | 2^1 | 2^0 --------------------------------------------0 1 1 0 0 0 0 1
1169
Index
0 + 1*64 + 1*32 + 0 + 0 + 0 + 0 + 1 = 97
Zahlensysteme
46.2 Bits und Bytes Das Bit ist die kleinste binäre Informationseinheit. Es kann »0« oder »1«, »an« oder »aus«, »gesetzt« oder »nicht gesetzt« sein. Bytes Acht Bits bezeichnet man als Byte. Auf dem Byte und seinen Vielfachen beruht die Rechnerarchitektur (Busgröße, Speicherorganisation etc.). Die Größen der Datentypen praktisch aller Programmiersprachen sind ebenfalls Vielfache des Bytes, da man Daten dieser Größe besonders effizient speichern und verarbeiten kann. Ein Byte kann Werte zwischen 0 und 255 (28-1) speichern. Words und DWords Zwei Byte (also 16 Bit) bilden ein »Word«; zwei Word bezeichnet man als DWord. Ein Word kann Werte zwischen 0 und 216-1 speichern. Ein DWord kann Werte zwischen 0 und 232-1 speichern.
46.3 Die Hexadezimalzahlen Hexadezimalzahlen sind Zahlen zur Basis 16. Da es keine eigenen Ziffern für die Darstellung der Ziffern größer 9 gibt, zieht man die ersten Buchstaben des Alphabets zu ihrer Repräsentation heran. Tabelle 46.1: Die ersten Zahlen
1170
Dezimal
Binär
Hexadezimal
0
0000
0
1
0001
1
2
0010
2
3
0011
3
4
0100
4
5
0101
5
6
0110
6
7
0111
7
8
1000
8
9
1001
9
10
1010
A
Die Hexadezimalzahlen
Dezimal
Binär
Hexadezimal
11
1011
B
12
1100
C
13
1101
D
14
1110
E
15
1111
F
Tabelle 46.1: Die ersten Zahlen (Forts.)
43
44 Will ein Programmierer Daten bitnah bearbeiten (gleichgültig, ob die Bits Zahlen oder andere Daten codieren), ist die Darstellung als Dezimalzahl meist ungünstig, da aus der Dezimalzahl nicht zu ersehen ist, wie das Bitmuster aussieht. Auch die Umrechnung zwischen den Zahlensystemen ist recht mühselig (siehe Abschnitt »46.1« zu Beginn des Anhangs).
45
46
Manche Programmiersprachen gestatten es dem Programmierer daher, Integer-Werte auch durch binäre Literale darzustellen. Diese Darstellung hat jedoch den Nachteil, dass die Literale schnell sehr groß und unübersichtlich werden.
47
Der goldene Mittelweg führt in diesem Fall über die Oktal- oder Hexadezimalzahlen. Da es sich bei den Basen dieser Zahlensysteme um Vielfache von 2 handelt (Oktal = 23, Hexadezimal = 24), gilt, dass jede Ziffer dieser Zahlen für 3 bzw. 4 Bitpositionen steht. dezimal
oktal
Bitmuster
63
077
00 111 111
54
066
00 110 110
40
050
00 101 000
dezimal
hexadezimal
Bitmuster
255
0xFF
1111 1111
238
0xEE
1110 1110
215
0xD7
1101 0111
Tabelle 46.2: Oktale Zahlen
Tabelle 46.3: Hexadezimale Zahlen
1171
Inhalt
47 Tabellen 43
47.1
44
Java-Schlüsselwörter
abstract
do
implements
protected
true
assert
double
import
public
try
boolean
else
inner*
rest*
var*
break
enum
instanceof
return
void
byte
extends
int
short
volatile
byvalue*
false
interface
static
while
case
final
long
strictfp
cast*
finally
native
super
catch
float
new
switch
char
for
null
synchronized
class
future*
operator*
this
const*
generic*
outer*
throw
continue
goto*
package
throws
default
if
private
transient
Tabelle 47.1: Schlüsselwörter von Java
45 46
47
1173
Index
Die mit * markierten Schlüsselwörter sind für zukünftige Erweiterungen reserviert oder entstammen anderen Programmiersprachen und wurden in die Liste aufgenommen, damit der Java-Compiler ihre Vorkommen leichter erkennen und als Fehler markieren kann. Die »Schlüsselwörter« false, true und null sind Literale und können daher ebenfalls nicht für eigene Variablennamen verwendet werden.
Tabellen
47.2 Java-Datentypen Tabelle 47.2: Die elementaren Datentypen
Typ
Größe
Beschreibung und Wertebereich
Beispiele
boolean
1
Für boolesche Wahrheitswerte (wie sie in Bedintrue gungen von Schleifen und Verzweigungen verwen- false det werden)
true (wahr) und false (falsch) char
2
Für einzelne Zeichen Wertebereich sind die ersten 65536 Zeichen des Unicode-Zeichensatzes
byte
1
Ganze Zahlen sehr kleinen Betrags -128 bis 127
short
2
Ganze Zahlen kleinen Betrags -32.768 bis 32.767
int
4
Standardtyp für ganze Zahlen -2147483648 bis 2147483647
long
8
Für sehr große ganze Zahlen -9223372036854775808 bis 9223372036854775807
float
double
4
8
Für Gleitkommazahlen geringer Genauigkeit
'a' '?' '\n' -3 0 98 -3 0 1205 -3 0 1000000 -3 0 1000000000000
+/-3,40282347*1038
123.56700 -3.5e10
Standardtyp für Gleitkommazahlen mit größerer Genauigkeit
123.456789 12005.55e-12
+/-1,79769313486231570*10308
47.3 Java-Operatoren Die folgende Tabelle listet die Operatoren nach ihrer Priorität geordnet auf. 1 ist die höchste Priorität:
1174
Java-Operatoren
Priorität
Operatoren
Bedeutung
1
() [] .
Methodenaufruf
++ –– +, – ~ ! ()
Inkrement
2
Arrayindex
Tabelle 47.3: Priorität und Assoziativität der Operatoren
Elementzugriff
43
Dekrement Vorzeichen Bitkomplement
44
logische Negation Typumwandlung
3
* / %
Multiplikation
+ – +
Addition
> >>>
Linksverschiebung
= instanceof
kleiner, kleiner gleich
== !=
gleich
& &
bitweises UND
^ ^
bitweises XOR
| |
bitweises ODER
11
&&
logisches UND
12
||
logisches ODER
13
?:
Bedingungsoperator
14
= *=, /=, %=, +=, –=, &=, ^=, |=, =, >>>=
Zuweisung
,
Komma-Operator
4
5
6
7
8
9
10
15
45
Division Modulo (Rest der Division)
46
Subtraktion
47
Konkatenation (Stringverkettung)
Rechtsverschiebung Rechtsverschiebung
größer, größer gleich Typüberprüfung eines Objekts
ungleich
logisches UND
logisches XOR
logisches ODER
zusammengesetzte Zuweisung
1175
Tabellen
47.4 Unicode-Zeichen (ASCII 0 bis 127) Tabelle 47.4: Unicode-Zeichen
1176
HexCode
Zeichen
HexCode
Zeichen
HexCode
Zeichen
HexCode
Zeichen
0000
NULL
0020
SP
0040
@
0060
`
0001
STX
0021
!
0041
A
0061
a
0002
SOT
0022
»
0042
B
0062
b
0003
ETX
0023
#
0043
C
0063
c
0004
EOT
0024
$
0044
D
0064
d
0005
ENQ
0025
%
0045
E
0065
e
0006
ACK
0026
&
0046
F
0066
f
0007
Ton
0027
'
0047
G
0067
g
0008
Backspace
0028
(
0048
H
0068
h
0009
hor. Tab
0029
)
0049
I
0069
i
000A
Neue Zeile
002A
*
004A
J
006A
j
000B
vert, Tab
002B
+
004B
K
006B
k
000C
Vorschub
002C
,
004C
L
006C
l
000D
Car. Return
002D
-
004D
M
006D
m
000E
SO
002E
.
004E
N
006E
n
000F
SI
002F
/
004F
O
006F
o
0010
DLE
0030
0
0050
P
0070
p
0011
DC1
0031
1
0051
Q
0071
q
0012
DC2
0032
2
0052
R
0072
r
0013
DC3
0033
3
0053
S
0073
s
0014
DC4
0034
4
0054
T
0074
t
0015
NAK
0035
5
0055
U
0075
u
0016
SYN
0036
6
0056
V
0076
v
0017
ETB
0037
7
0057
W
0077
w
0018
CAN
0038
8
0058
X
0078
x
0019
EM
0039
9
0059
Y
0079
y
001A
SUB
003A
:
005A
Z
007A
z
001B
ESC
003B
;
005B
[
007B
{
Unicode-Zeichen (Umlaute und Sonderzeichen)
HexCode
Zeichen
HexCode
Zeichen
HexCode
Zeichen
HexCode
Zeichen
001C
FS
003C
<
005C
\
007C
|
001D
CAN
003D
=
005D
]
007D
}
001E
EM
003E
>
005E
^
007E
~
001F
SUB
003F
?
005F
_
007F
DEL
Tabelle 47.4: Unicode-Zeichen (Forts.)
43
44 47.5 Unicode-Zeichen (Umlaute und Sonderzeichen)
45
HexCode
Zeichen
HexCode
Zeichen
HexCode
Zeichen
HexCode
Zeichen
00A0
NBSP
00B8
¸
00D0
Ð
00E8
è
00A1
¡
00B9
¹
00D1
Ñ
00E9
é
00A2
¢
00BA
º
00D2
Ò
00EA
ê
00A3
£
00BB
»
00D3
Ó
00EB
ë
00A4
¤
00BC
¼
00D4
Ô
00EC
ì
00A5
¥
00BD
½
00D5
Õ
00ED
í
00A6
¦
00BE
¾
00D6
Ö
00EE
î
00A7
§
00BF
¿
00D7
×
00EF
ï
00A8
¨
00C0
À
00D8
Ø
00F0
ð
00A9
©
00C1
Á
00D9
Ù
00F1
ñ
00AA
ª
00C2
Â
00DA
Ú
00F2
ò
00AB
«
00C3
Ã
00DB
Û
00F3
ó
00AC
¬
00C4
Ä
00DC
Ü
00F4
ô
00AD
-
00C5
Å
00DD
Ý
00F5
õ
00AE
®
00C6
Æ
00DE
Þ
00F6
ö
00AF
¯
00C7
Ç
00DF
ß
00F7
÷
00B0
°
00C8
È
00E0
à
00F8
ø
00B1
±
00C9
É
00E1
á
00F9
ù
00B2
²
00CA
Ê
00E2
â
00FA
ú
00B3
³
00CB
Ë
00E3
ã
00FB
û
Tabelle 47.5: Unicode-Zeichen (Umlaute und Sonderzeichen)
46 47
1177
Tabellen
Tabelle 47.5: Unicode-Zeichen (Umlaute und Sonderzeichen) (Forts.)
HexCode
Zeichen
HexCode
Zeichen
HexCode
Zeichen
HexCode
Zeichen
00B4
´
00CC
Ì
00E4
ä
00FC
ü
00B5
µ
00CD
Í
00E5
å
00FD
ý
00B6
¶
00CE
Î
00E6
æ
00FE
þ
00B7
·
00CF
Ï
00E7
ç
00FF
ÿ
47.6 Unicode-Zeichen (griechisches Alphabet) Tabelle 47.6: Unicode-Zeichen (griechisches Alphabet)
1178
HexCode
Zeichen
HexCode
Zeichen
HexCode
Zeichen
0391
Α
03A1
Ρ
03BA
κ
0392
Β
03A3
Σ
03BB
λ
0393
Γ
03A4
Τ
03BC
µ
0394
∆
03A5
Υ
03BD
ν
0395
Ε
03A6
Φ
03BE
ξ
0396
Ζ
03A7
Χ
03BF
ο
0397
Η
03A8
Ψ
03C0
π
0398
Θ
03B1
α
03C1
ρ
0399
Ι
03B2
β
03C2
ς
039A
Κ
03B3
γ
03C3
σ
039B
Λ
03B4
δ
03C4
τ
039C
Μ
03B5
ε
03C5
υ
039D
Ν
03B6
ζ
03C6
ϕ
039E
Ξ
03B7
η
03C7
χ
039F
Ο
03B8
θ
03C8
ψ
03A0
Π
03B9
ι
03C9
ω
HTTP-Statusnummern
47.7 HTTP-Statusnummern Status-Nummer
Konstante in javax.servlet.HttpServletResponse
Erfolgsstatus 2xx 200 OK
SC_ OK
201 Created
SC_ CREATED
202 Accepted
SC_ ACCEPTED
203 Non Authoritative Information
SC_ NON_AUTHORITATIVE_INFORMATION
204 No Content
SC_ NO_CONTENT
205 Reset Content
SC_ RESET_CONTENT
206 Partial Content
SC_ PARTIAL_CONTENT
Tabelle 47.7: HTTP-Statusnummern
43
44
45
46
Weiterleitung (Redirection) 3xx 300 Multiple Choices
SC_MULTIPLE_CHOICES
301 Moved Permanently
SC_MOVED_PERMANENTLY
302 Moved Temporarily
SC_MOVED_TEMPORARILY
303 See Other
SC_SEE_OTHER
304 Not Modified
SC_NOT_MODIFIED
305 Use Proxy
SC_USER_PROXY
47
Client Fehler 4xx 400 Bad Request
SC_BAD_REQUEST
401 Unauthorized
SC_UNAUTHORIZED
402 Payment Required
SC_PAYMENT_REQUIRED
403 Forbidden
SC_FORBIDDEN
404 Not Found
SC_NOT_FOUND
405 Method Not Allowed
SC_METHOD_NOT_ALLOWED
406 Not Acceptable
SC_NOT_ACCEPTABLE
407 Proxy Authentication Required
SC_PROXY_AUTHENTICATION_REQUIRED
408 Request Timeout
SC_REQUEST_TIMEOUT
409 Conflict
SC_CONFLICT
410 Gone
SC_GONE
1179
Tabellen
Tabelle 47.7: HTTP-Statusnummern (Forts.)
Status-Nummer
Konstante in javax.servlet.HttpServletResponse
Erfolgsstatus 2xx 411 Length Required
SC_LENGTH_REQUIRED
412 Precondition Failed
SC_PRECONDITION_FAILED
413 Request Entity Too Large
SC_REQUEST_ENTITY_TOO_LARGE
414 Request-URI Too Long
SC_REQUEST_URI_TOO_LONG
415 Unsupported Media Type
SC_UNSUPPORTED_MEDIA_TYPE
416 Requested range not satisfiable
SC_REQUESTED_RANGE_NOT_SATISFIABLE
417 Expectation failed
SC_EXPECTATION_FAILED
Server Fehler 5xx
1180
500 Internal Server Error
SC_INTERNAL_SERVER_ERROR
501 Not Implemented
SC_NOT_IMPLEMENTED
502 Bad Gateway
SC_BAD_GATEWAY
503 Service Unavailable
SC_SERVICE_UNAVAILABLE
504 Gateway Timeout
SC_GATEWAY_TIMEOUT
505 HTTP Version Not Supported
SC_HTTP_VERSION_NOT_SUPPORTED
Index
! ■■■■■■■■■■ " 184, 272 % 156 %n (Platzhalter) 96 & (bitweises UND) 159 && 188 () (Methoden) 230 () (Typumwandlung) 155 * 155 + (Addition) 155 + (Konkatenation) 168 + (Vorzeichen) 155 ++ 157 .NET 41 / 156 184 >= 184 >> 161 >>> 161 ? 194 ? (Generics-Wildcard) 406 @Deprecated 1097 @Documented 1098 @Generated 1098 @Inherited 1098 @InjectionComplete 1098 @Override 1098 @PostConstruct 1099 @Predestroy 1099 @Resource 1098 @Retention 1098 @SOAPBinding 1132
@SuppressWarnings 1098 @Target 1098 @WebMethod 1132 @WebParam 1132 @Webresult 1132 @WebService 1132 \n (Zeilenumbruch) 136 \t (Tabulator) 136 ^ (bitweises XOR) 159 ^ (logisches XOR) 189 {} (Anweisungsblöcke) 152 {} (Array-Initialisierung) 273 | (bitweises ODER) 159 | (logisches ODER) 189 || 189 ~ 160
A ■■■■■■■■■■ abs() (Math) 162 absolute() (ResultSet) 940 abstract (für Klassen) 219, 418 abstract (für Methoden) 418 Abstract Window Toolkit (AWT) 472 AbstractButton 525, 530, 618 AbstractTableModel 683 accept() (FileFilter) 563, 835 accept() (FilenameFilter) 834 accept() (ServerSocket) 977, 983 accept() (ServerSocketChannel) 997 AccessControlException 1086 acos() (Math) 162 Action 623 ActionEvent 497, 499, 502, 524 ActionListener 430, 500 ActionListener (für Menübefehle) 550 ActionListener (für Schaltflächen) 495 ActionListener (für Timer) 826 actionPerformed() (ActionListener) 495, 500, 550 activeCount() (ThreadGroup) 819 ActiveX 40 Ada Lovelace 111
1181
Index
- (Subtraktion) 155 - (Vorzeichen) 155 ! 190 -- 157 != 154, 184
Index
Adapterklassen 501 add() (ButtonGroup) 534 add() (Collection) 762 add() (Component) 477 add() (Container) 479 add() (DefaultMutableTreeNode) 679 add() (JMenu) 546 add() (JPopupMenu) 549 add() (List) 764 add() (ListIterator) 782 add() (Set) 771 add() (SystemTray) 694 addActionListener() (AbstractButton) 531 addActionListener() (Component) 499 addActionListener() (JMenuItem) 547 addActionListener() (TrayIcon) 694 addAll() (Collection) 762 addAll() (Collections) 787 addAll() (List) 764 addAll() (Set) 771 addChangeListener() (AbstractButton) 531 addChoosableFileFilter() (JFileChooser) 562 addFlavorListener() (Clipboard) 556 addFocusListener() (Component) 499 addItem() (JComboBox) 541, 543 addItemListener() (AbstractButton) 531 addItemListener() (Component) 499 addKeyListener() (Component) 499 addMouseListener() (Component) 499 addMouseListener() (TrayIcon) 694 addMouseMotionListener() (Component) 499 addMouseMotionListener() (TrayIcon) 694 addMouseWheelListener() (Component) 499 addSeparator() (JMenu) 546 addSeparator() (JPopupMenu) 549 addShutdownHook() (Runtime) 750 addTab() (JTabbedPane) 674 addTextListener() (Component) 499 addWindowListener() (Component) 475, 499 addXXXListener() (Component) 477 after() (Date) 743 afterLast() (ResultSet) 940 Aktionen 622 Algol 52 Allgemeine Programmiergrundlagen 43 Anweisungen 54 Anweisungsblöcke 54 Assembler 44 Bedingungen 49 Bibliotheken 62 Bildschirmausgaben 48 Compiler 46 Datentypen 53 Deklaration 54
1182
Eintrittspunkt 60 Felder 65 Funktionen 58 – Aufruf 59 – Definition 59 – Parameter 59 – Rückgabewert 59 Gleitkommazahlen 53 Instanzen 65 Integer 53 Interpreter 46 Klassen 62 – Definition 70 – Instanzbildung 70 – Konstruktoren 72 – Sicherheit 73 – statische Elemente 75 Kommentare 47 Konsole 48 Kontrollstrukturen 49 Linker 46 Literale 47 Methoden 67 Modularisierung 58 Objekte 65 Objektvariablen 65 Operatoren 48 Plattformunabhängigkeit 80 Programmstart 60 Prozessor 44 Referenztypen 70 Register 44 Rundungsfehler 132, 330 Schleifen 51 Schlüsselwörter 54 Sprünge 49 Strings 45 Typumwandlungen 57 Überladung 72 Variablen 45 Vererbung 76 Vergleiche 50 Verzweigungen 50 Zufallszahlen 165 allocate() (ByteBuffer) 880, 882 allocateDirect() (ByteBuffer) 887 Alpha-Kanal 580 Animationen 590 Annotationen 1097 @Deprecated 1097 @Documented 1098 @Generated 1098 @Inherited 1098 @InjectionComplete 1098
Index
java.awt.event.MouseEvent 499, 588 java.awt.event.MouseListener 500, 550 java.awt.event.MouseMotionAdapter 500 java.awt.event.MouseMotionListener 500 java.awt.event.MouseWheelEvent 499 java.awt.event.MouseWheelListener 500 java.awt.event.TextEvent 499 java.awt.event.TextListener 500 java.awt.event.WindowAdapter 500 java.awt.event.WindowEvent 499 java.awt.event.WindowListener 500 java.awt.FlowLayout 487 java.awt.Font 575 java.awt.FontMetrics 583 java.awt.Frame 474 java.awt.geom.Ellipse2D 600 java.awt.geom.GeneralPath 601 java.awt.geom.Line2D 600 java.awt.GradientPaint 599 java.awt.Graphics 572, 650 java.awt.Graphics2D 596 java.awt.GridBagConstraints 492 java.awt.GridBagLayout 492 java.awt.GridLayout 488 java.awt.Image 604 java.awt.image.BufferedImage 604 java.awt.image.RenderedImage 604 java.awt.ItemEvent 532, 539 java.awt.ItemListener 532, 539, 542 java.awt.Label 476, 481 java.awt.List 476 java.awt.MediaTracker 1053 java.awt.Paint 598 java.awt.Panel 476, 485 java.awt.Point 589 java.awt.Polygon 600 java.awt.print.PageFormat 650 java.awt.print.Printable 648, 650 java.awt.print.PrinterJob 647, 652 java.awt.Rectangle 600 java.awt.ScrollPane 476 java.awt.SplashScreen 691 java.awt.SystemColor 579 java.awt.TextArea 476 java.awt.TextComponent 476 java.awt.TextField 476 java.awt.TexturePaint 598 java.awt.Toolkit 557 java.awt.TrayIcon 693 java.awt.Window 476 java.io.AudioInputStream 708, 710 java.io.BufferedInputStream 842 java.io.BufferedOutputStream 842 java.io.BufferedReader 100, 842, 978
1183
Index
@Override 1098 @PostConstruct 1099 @Predestroy 1099 @Resource 1098 @Retention 1098 @SOAPBinding 1132 @SuppressWarnings 1098 @Target 1098 @WebMethod 1132 @WebParam 1132 @WebResult 1132 @WebService 1132 Parameter 1100 selbst definierte 1099 vordefinierte 1097 Anonyme Klassen 322, 430, 497 Antitrust Case 39 Anweisungen 54, 152 Anweisungsblöcke 54 auskommentieren 107 leere Anweisungen 153, 212 static-Blöcke 269 API java.applet.Applet 1041 java.applet.AudioClip 704, 1053 java.awt.AWTEvent 494 java.awt.BasicStroke 597 java.awt.BorderLayout 490 java.awt.Button 476, 483 java.awt.Canvas 476, 573 java.awt.CardLayout 491 java.awt.Checkbox 476, 483 java.awt.CheckboxGroup 484 java.awt.Choice 476 java.awt.Color 578 java.awt.Component 476 java.awt.Container 476 java.awt.datatransfer.Clipboard 555 java.awt.datatransfer.ClipboardOwner 557 java.awt.datatransfer.DataFlavor 557 java.awt.datatransfer.StringSelection 556 java.awt.datatransfer.Transferable 556 java.awt.Event 553 java.awt.event.ActionEvent 497, 499, 502 java.awt.event.ActionListener 430, 495, 500, 550, 826 java.awt.event.FocusAdapter 500 java.awt.event.FocusEvent 499 java.awt.event.FocusListener 500 java.awt.event.ItemEvent 499 java.awt.event.ItemListener 500, 550 java.awt.event.KeyAdapter 500 java.awt.event.KeyEvent 499, 554 java.awt.event.KeyListener 500 java.awt.event.MouseAdapter 500, 588
Index
java.io.BufferedWriter 842, 978 java.io.ByteArrayInputStream 850 java.io.ByteArrayOutputStream 850 java.io.CharArrayReader 852 java.io.CharArrayWriter 852 java.io.Console 96, 97, 874 java.io.DataInputStream 849 java.io.DataOutputStream 849 java.io.Externalizable 862 java.io.File 604, 636, 829 java.io.FileFilter 835 java.io.FileInputStream 840 java.io.FilenameFilter 834 java.io.FileOutputStream 840 java.io.FileReader 636, 840 java.io.FileWriter 636, 840 java.io.InputStream 839, 977 java.io.InputStreamReader 100, 847 java.io.LineInputReader 842 java.io.LineNumberReader 741 java.io.ObjectInputStream 858 java.io.ObjectOutputStream 858 java.io.OutputStream 840, 977 java.io.OutputStreamReader 847 java.io.PipedInputStream 813 java.io.PipedOutputStream 813 java.io.PipedReader 813 java.io.PipedWriter 813 java.io.PrintStream 846 java.io.PrintWriter 846, 980 java.io.PushbackInputStream 849 java.io.PushbackReader 849 java.io.RandomAccessFile 843 java.io.Reader 838 java.io.Serializable 859, 1026 java.io.StreamTokenizer 855 java.io.StringReader 852 java.io.StringWriter 852 java.io.Writer 838 java.lang.Boolean 145 java.lang.Byte 145 java.lang.Character 145, 731 java.lang.Class 398 java.lang.ClassLoader 1084, 1085 java.lang.Cloneable 455 java.lang.Comparable 466, 784 java.lang.Double 145 java.lang.Float 145 java.lang.Integer 100, 145 java.lang.Long 145 java.lang.Math 102, 162 java.lang.Object 171, 380, 399 java.lang.Process 751 java.lang.Runnable 799
1184
java.lang.Runtime 750 java.lang.SecurityManager 1086 java.lang.Short 145 java.lang.String 102, 137, 169 java.lang.StringBuffer 733 java.lang.StringBuilder 733 java.lang.System 752, 853 java.lang.System.err 853 java.lang.System.in 853 java.lang.System.out 853 java.lang.Thread 793 java.lang.ThreadGroup 818 java.lang.Throwable 441 java.math.BigDecimal 325 java.math.BigInteger 325 java.net.DatagramPacket 984 java.net.DatagramSocket 984 java.net.HttpURLConnection 1004, 1011, 1015 java.net.InetAddress 974 java.net.InetSocketAddress 976 java.net.MulticastSocket 989 java.net.ServerSocket 977, 983 java.net.Socket 983 java.net.URL 1002 java.net.URLConnection 1004 java.net.URLDecoder 1004 java.net.URLEncoder 1004 java.nio.Buffer 878 java.nio.ByteBuffer 878 java.nio.channels.Channel 878 java.nio.channels.DatagramChannel 878, 998 java.nio.channels.FileChannel 878, 892 java.nio.channels.FileLock 892 java.nio.channels.SelectionKey 994 java.nio.channels.Selector 993, 996 java.nio.channels.ServerSocketChannel 994 java.nio.channels.SocketChannel 878, 994 java.nio.charset.Charset 884 java.nio.charset.CharsetDecoder 886 java.nio.charset.CharsetEncoder 886 java.nio.IntBuffer 878 java.nio.MappedByteBuffer 889 java.rmi.Naming 1022 java.rmi.registry.LocateRegistry 1039 java.rmi.Remote 1021 java.rmi.RMISecurityManager 1032 java.rmi.server.UnicastRemoteObject 1040 java.sql.Blob 957 java.sql.Clob 957 java.sql.Connection 933, 950 java.sql.DatabaseMetaData 947 java.sql.Date 956 java.sql.DriverManager 933, 934 java.sql.PreparedStatement 952
Index
javax.imageio.ImageIO 604 javax.print.attribute.AttributeSet 648, 658 javax.print.attribute.HashPrintRequest AttributeSet 658 javax.print.attribute.PrintRequestAttributeSet 658 javax.print.DocFlavor 655 javax.print.PrintService 655 javax.print.PrintServiceLookup 648, 655 javax.print.SimpleDoc 657 javax.sound.sampled.AudioFileFormat 707 javax.sound.sampled.AudioFormat 707, 710 javax.sound.sampled.AudioSystem 707 javax.sound.sampled.Clip 707 javax.sound.sampled.DataLine 707 javax.sound.sampled.Line 707 javax.sound.sampled.Mixer 707 javax.sound.sampled.Port 707 javax.sound.sampled.SourceDataLine 707, 708, 710 javax.sound.sampled.TargetDataLine 707 javax.swing.AbstractButton 525, 530, 618 javax.swing.Action 623 javax.swing.Box 513 javax.swing.BoxLayout 513 javax.swing.ButtonGroup 534 javax.swing.ButtonModel 618 javax.swing.Component 527 javax.swing.event.ChangeListener 568 javax.swing.event.DocumentListener 632 javax.swing.event.ListSelectionEvent 688 javax.swing.event.ListSelectionListener 541, 687, 688 javax.swing.event.TableModelListener 687, 688 javax.swing.filechooser.FileFilter 562 javax.swing.Group 515 javax.swing.GroupLayout 515 javax.swing.GroupLayout.Alignment 516 javax.swing.GroupLayout.ParallelGroup 515 javax.swing.GroupLayout.SequentialGroup 515 javax.swing.ImageIcon 852 javax.swing.JApplet 1041 javax.swing.JButton 525, 531 javax.swing.JCheckBox 526, 618 javax.swing.JCheckBoxMenuItem 525 javax.swing.JCheckButton 536 javax.swing.JColorChooser 564 javax.swing.JComboBox 526, 540 javax.swing.JDesktopPane 526, 676 javax.swing.JDialog 565 javax.swing.JEditorPane 527, 619 javax.swing.JFileChooser 561 javax.swing.JFormattedTextField 527 javax.swing.JFrame 509 javax.swing.JInternalFrame 676 javax.swing.JLabel 526, 529, 699 javax.swing.JLayeredPane 510, 526
1185
Index
java.sql.ResultSet 937 java.sql.ResultSetMetaData 949 java.sql.SQLException 960 java.sql.SQLWarning 961 java.sql.Statement 935 java.text.Collator 187, 731, 917 java.text.DateFormat 745, 914 java.text.NumberFormat 914 java.text.SimpleDateFormat 743, 745 java.util.ArrayDeque 774 java.util.ArrayList 765 java.util.Arrays 277 java.util.Calendar 744 java.util.Collection 758 java.util.Collections 787, 828 java.util.Comparator 785 java.util.Date 742 java.util.Deque 765, 773 java.util.Enumeration 782 java.util.EnumMap 778 java.util.EnumSet 770 java.util.EventObject 494, 502 java.util.Formatter 870 java.util.GregorianCalendar 744 java.util.HashMap 778 java.util.HashSet 770 java.util.Hashtable 623, 778 java.util.Iterator 758, 781 java.util.LinkedHashMap 778 java.util.LinkedHashSet 770 java.util.LinkedList 765, 773 java.util.List 758, 764, 765 java.util.ListIterator 781 java.util.Locale 907 java.util.Map 758, 777 java.util.NavigableMap 778 java.util.NavigableSet 770 java.util.PriorityQueue 774 java.util.Properties 748 java.util.Queue 758 java.util.Random 166 java.util.regex.Matcher 739 java.util.regex.Pattern 738, 741 java.util.ResourceBundle 896 java.util.Scanner 100, 863 java.util.Set 758, 771 java.util.Stack 766 java.util.StringTokenizer 736, 857 java.util.Timer 753 java.util.TimerTask 753 java.util.TreeMap 778, 786 java.util.TreeSet 770, 785, 786 java.util.Vector 765 java.util.WeakHashMap 778
Index
javax.swing.JList 526, 540 javax.swing.JMenu 525, 546 javax.swing.JMenuBar 526, 546 javax.swing.JMenuItem 525, 546, 548 javax.swing.JOptionPane 526, 559 javax.swing.JPanel 526, 574 javax.swing.JPasswordField 527, 624 javax.swing.JPopupMenu 526, 549 javax.swing.JProgressBar 526, 543 javax.swing.JRadioButton 526, 539 javax.swing.JRadioButtonMenuItem 525, 548 javax.swing.JRootPane 510 javax.swing.JScrollBar 526 javax.swing.JScrollPane 526, 623, 629 javax.swing.JSlider 526, 565 javax.swing.JSpinner 526 javax.swing.JSplitPane 526, 673 javax.swing.JTabbedPane 526, 673 javax.swing.JTable 527, 682 javax.swing.JTextArea 527, 627, 700 javax.swing.JTextComponent 527 javax.swing.JTextField 527, 624 javax.swing.JTextPane 527, 619 javax.swing.JToggleButton 526, 532, 637 javax.swing.JToolBar 527, 554 javax.swing.JToolTip 527 javax.swing.JTree 527, 677 javax.swing.Keystroke 553 javax.swing.OverlayLayout 513 javax.swing.Scrollable 623 javax.swing.ScrollPaneLayout 513 javax.swing.SpringLayout 513 javax.swing.SwingUtilities 822 javax.swing.table.AbstractTableModel 683 javax.swing.table.TableColumnModel 683 javax.swing.table.TableModel 683 javax.swing.text.DefaultStyledDocument 619 javax.swing.text.Document 619 javax.swing.text.JTextComponent 617, 649, 660 javax.swing.text.Keymap 623 javax.swing.text.PlainDocument 619, 628, 634 javax.swing.text.StyledDocument 619 javax.swing.Timer 825 javax.swing.TransferHandler 699, 701 javax.swing.tree.DefaultMutableTreeNode 678 javax.swing.tree.DefaultTreeModel 678 javax.swing.tree.DefaultTreeSelectionModel 681 javax.swing.tree.TreeModel 678 javax.swing.tree.TreeNode 678 javax.swing.tree.TreePath 682 javax.swing.UIManager 520 javax.swing.ViewportLayout 513 javax.xml.parsers.DocumentBuilder 1118
1186
javax.xml.parsers.SAXParserFactory 1113 javax.xml.ws.Endpoint 1133 org.w3c.dom.Document 1118 org.w3c.dom.Node 1118 org.xml.sax.Attributes 1114 org.xml.sax.helpers.DefaultHandler 1113 Quelltexte 85 sun.misc.BASE64Encoder 1015 System.err 432, 853 System.in 97, 853 System.out 95, 853 append() (JTextArea) 627 append() (StringBuffer) 734 append() (StringBuilder) 734 appendChild() (Node) 1124 Applet 1041 Applets 79, 1041 anzeigen – im AppletViewer 1046 – im Browser 1046 AWT 1043 Basisklassen 1041 Bilddateien laden 1053 Datenbanktreiber 933 Datenbankzugriffe 962 Grundgerüst 1041 JAR-Archive 1060 java.applet.Applet 1041 Java-Plug-In 81, 962, 1042, 1048 javax.swing.JApplet 1041 Kompatibilität zu Browsern 1042 kompilieren 1045 Layout-Managing 1044 Lebenszyklus 1043 Methoden 1043 – destroy() 1043 – getAudioClip() 1053 – getCodeBase() 1053 – getImage() 1053 – getParameter() 1055 – init() 1043, 1055 – paint() 1043 – start() 1043 – stop() 1043 Parameter 1054 – in Applet abfragen 1055 – in HTML-Code definieren 1054 Sicherheit 1061 – policies 1062 – Restriktionen 1062 – Sandbox-Prinzip 1062 Sicherheitsbeschränkungen 1086 Sounddateien laden 1053
Index
Speicherbelegung 274 Unterarrays 290 Variablen deklarieren 272 vergleichen 279 vergrößern 278 von Arrays 289 von Basisklassentypen 391 von Objekten 274 ASCII 134 asin() (Math) 163 Assembler 44 AT&T 39 atan() (Math) 163 atan2() (Math) 163 Attributes 1114 AttributeSet 648, 658 AudioClip 704, 1053 AudioFileFormat 707 AudioFormat 707, 710 AudioInputStream 708, 710 AudioSystem 707 Aufzählungen (enum) 291 definieren 292 eigene Klassen 296 for-Schleife 294 Geschichte 291 Konstruktor 296 Methoden 295 switch-Verzweigung 293 vergleichen 293 Ausdrücke Aufbau 152 Boolesche 183 Definition 152 Nebeneffekte 179, 214 Ausgaben abstrakte Basisklasse (OutputStream) 837 auf Konsole 94 – print() 95 – printf() 869 – println() 95 byteorientiertes Schreiben 837 Gleitkommazahlen 452, 872 in Dateien schreiben 840 java.io.OutputStream 839 New Java I/O 877 Pufferung 842 Standardausgabe 853 Umlaute 94 Writer 840 zeichenorientiertes Schreiben 838 Ausnahmebehandlung siehe Exception-Behandlung Autoboxing 147, 759
1187
Index
Swing 1050 testen 1048 – Java-Konsole 1048 – Java-Plug-In-Dialogfeld 1048 Threads 1051 Webseite 1045 – applet-Tag 1046, 1047 – HtmlConverter 1047 – Tag 1047, 1054 applet-Tag 1046, 1047 AppletViewer 1046 Archive (jar) siehe JAR-Archive ArithmeticException 156 array() (ByteBuffer) 882 arraycopy() (System) 752 ArrayDeque 774 ArrayIndexOutOfBoundsException 275 ArrayList 765 Arrays 271, 277 als Parameter 282 Anzahl Elemente – abfragen 277 – festlegen 273 args 283 ausgeben 278 definieren 271 durchsuchen 281 Elemente – durchlaufen 275 – Gültigkeitsbereich 307 – Lebensdauer 311 – Zugriff 274 erzeugen 272 füllen 278 in Schleifen durchlaufen 275 Indizierung 275 initialisieren 273 java.util.Arrays 277 kopieren 278 Länge – abfragen 277 – festlegen 273 length 277 mehrdimensionale 285 – definieren 285 – durchlaufen 286 – dynamisch erzeugte Unterarrays 289 – erzeugen 285 – Initialisierung 285 – Speicherbelegung 286 – Zugriff auf Elemente 285 parametrisierte 407 sortieren 280
Index
availableCharsets() (Charset) 884 AWT 472 Applets 1043 Canvas 573 Ereignisbehandlung 494 Grafik 581 Komponenten 476, 513 Layout-Manager 486 paint() 572, 585 Pakete 472 versus Swing 507 AWTEvent 494
B ■■■■■■■■■■ Babbage, Charles 111 Backslash-Zeichen 136 BASE64Encoder 1015 BasicStroke 597 Basisklassen Abstrakte 418 abstrakte 415 Arrays 391 konstruktoren 366 – Argumente übergeben 369 – Aufruf 368 – Auswahl 370 – Standardkonstruktor 367 Parameter 393 Batchläufe 953 Baumdarstellungen (JTree) 677 Bedingungen 49, 181 Beenden Methoden 241 Programme 435, 474, 510, 705, 750 Befehlszeilenargumente 283 before() (Date) 743 beforeFirst() (ResultSet) 940 Benutzereingaben Typumwandlung 143, 144 überprüfen 432 Benutzeroberflächen, grafische (GUI) 471 Beschriftungsfelder 481, 526, 529 Bestätigungsdialoge 560 Bezeichner 105, 120 Klassennamen 509 qualifizierte 254 Bibliotheken 62 BigDecimal 325 BigInteger 325
1188
Bilder 604 als Ressourcen 901 anzeigen 605 bearbeiten 606 BufferedImage-Klasse 604 drucken 661 Image-Klasse 604 in Applets 1053 laden 604 speichern 604 Bildlaufbereiche 526 Bildlaufleisten 526, 623, 629 Bildschirmausgaben 48 Bill Gates 35 binarySearch() (Arrays) 281, 282 binarySearch() (Collections) 787 bind() (Naming) 1022 bind() (ServerSocket) 996 Bindung dynamische 387 statische 387 Bits 1170 Blob 957 Bogenmaß 165 Boolean 136, 145, 184 Boolesche Werte 136 Boolescher Typ (boolean) 136, 184 Bootstrap-Classloader 1084 BorderLayout 490 BorderLayout-Manager 490 Box 513 BoxLayout 513 BoxLayout-Manager 513 break 195, 208, 211 Browser Applets-Unterstützung 1042 Browser-Krieg 40 HotJava 37 Internet Explorer 38 Java-Konsole 1048 Java-Plug-In 81, 962, 1042, 1048 Mosaic-Browser 37 Buch-CD 83 Buffer 878 Buffer Views 883 BufferedImage 604 BufferedInputStream 842 BufferedOutputStream 842 BufferedReader 100, 842, 978 BufferedWriter 842, 978 Button 476, 483 ButtonGroup 534 ButtonModel 618
Index
C ■■■■■■■■■■ C 52 C# 41 Calendar 744 cancel() (SwingWorker) 823 cancel() (Timer) 753 canExecute() (File) 830 canImport() (TransferHandler) 701 canRead() (File) 830 Canvas 476, 573 canWrite() (File) 830 capacity() (ByteBuffer) 882 CardLayout 491 CardLayout-Manager 491 Caret 621 case 194 CASE-Tools 259 Casten 144, 155, 384 catch 438 cbrt() (Math) 163 ceil() (Math) 163 Cern-Institut 37 changedUpdate() (DocumentListener) 632 ChangeListener 568 Channel 878 channel() (FileLock) 892 channel() (SelectionKey) 994, 997 char 125, 133 Character 145, 731 characters () (DefaultHandler) 1113 CharArrayReader 852 CharArrayWriter 852 charAt() (String) 146, 170 charAt() (StringBuffer) 734 charAt() (StringBuilder) 734 Charles Babbage 111 Charset 884 CharsetDecoder 886 CharsetEncoder 886 Checkbox 476, 483 CheckboxGroup 484 children() (DefaultMutableTreeNode) 679
Choice 476 Class 398 Class-Dateien 1143 ClassLoader 1084, 1085 benutzerdefinierte 1084 Bootstrap 1084 Erweiterungs-ClassLoader 1084 System 1084 ClassNotFoundException 441 CLASSPATH, Umgebungsvariable setzen 88 clear() (ByteBuffer) 882 clear() (Collection) 762 clear() (Map) 777 clear() (Set) 771 clearRect() (Graphics) 578 clearSelection() (JList) 542 clearSelection() (JTable) 686 Clip 707 clip() (Graphics2D) 613, 614 Clipboard 555 ClipboardOwner 557 Clipping 611 Device-Clip 612 Effekte 613 effektives Neuzeichnen 612 User-Clip 612 clipRect() (Graphics) 613, 614 Clob 957 clone() (Aufzählungen) 295 clone() (Locale) 908 clone() (Object) 173, 454 überschreiben 455 Cloneable 455 CloneNotSupportedException 456 close() (Connection) 935 close() (DatagramSocket) 984, 986 close() (FileChannel) 881 close() (InputStream) 839 close() (OutputStream) 840 close() (RandomAccessFile) 843 close() (Scanner) 865 close() (ServerSocket) 983 close() (ServerSocketChannel) 997 close() (Socket) 978, 980 close() (SourceDataLine) 710 close() (SplashScreen) 691 closePath() (GeneralPath) 601 Cobol 52 Codebase 1033 Collator 187, 731, 917 Collection 758, 787, 828 synchronisieren 828 Threads 828
1189
Index
Byron 111 Byte 125, 145, 1170 ByteArrayInputStream 850 ByteArrayOutputStream 850 ByteBuffer 878 Bytecode 82 Bytecode-Verifier 1084
Index
Color 578 columnAtPoint() (JTable) 687 commit() (Connection) 950 compact() (ByteBuffer) 880, 882 Comparable 466, 784 Comparator 785 compare() (Collator) 187, 731, 917 compare() (Comparator) 785 compareTo() (Aufzählungen) 293 compareTo() (Comparable) 784 überschreiben 465 compareTo() (Date) 743 compareTo() (String) 170, 186 compile() (Pattern) 738 Compiler 46, 82, 1139 Arbeitsweise 1140 Aufruf 92, 1146 Benennung der Quelldateien 1143 Class-Dateien 1143 Cross-Compiler 80 deprecation-Warnung 1146 JITter 82 Klassenpfad 1140 Optionen 1144 Quellpfad 1140 unchecked-Warnung 1146 Component 476 concat() (String) 170 Concurrent-Bibliothek 760 configureBlocking() (DatagramChannel) 999 configureBlocking() (ServerSocketChannel) 996, 997 configureBlocking() (SocketChannel) 998 ConnectException 982 Connection 933, 950 Console 96, 97, 874 console() (Console) 96 console() (System) 874 Container 473, 476 AWT 476 ContentPane 510, 511 Frame 474 JDesktopPane 676 JInternalFrame 676 JPanel 512 JSplitPane 673 JTabbedPane 673 Komponenten einfügen 479 Layout-Manager 486, 512 Panel 485 Swing 512, 673 Container (Collection-Klassen) 757 Autoboxing 759 Basisschnittstelle 758 Concurrent-Bibliothek 760
1190
elementare Daten 759 elementare Operationen 761 Enumeration-Schnittstelle 782 erzeugen 760 for-Schleife 783 Größe 761 Hashtabellen 776 Hashtabellen siehe Container (Collection-Klassen) – Wörterbücher (Map) Iteratoren 780 – erzeugen 781 – verwenden 781 java.util.ArrayDeque 774 java.util.ArrayList 765 java.util.Collections 787 java.util.Deque 765, 773 java.util.EnumMap 778 java.util.EnumSet 770 java.util.HashMap 778 java.util.HashSet 770 java.util.Hashtable 778 java.util.LinkedHashMap 778 java.util.LinkedHashSet 770 java.util.LinkedList 765, 773 java.util.List 764, 765 java.util.Map 777, 778 java.util.NavigableMap 778 java.util.NavigableSet 770 java.util.PriorityQueue 774 java.util.Queue 774, 775 java.util.Set 771 java.util.Stack 766 java.util.TreeMap 778, 786 java.util.TreeSet 770, 785, 786 java.util.Vector 765 java.util.WeakHashMap 778 Konstruktoren 761 Listen 763 – Eigenschaften 763 – Methoden 764, 765 Mengen (Set) 770 – Eigenschaften 770 – Methoden 770, 771 Referenztyp-Elemente 759 sortieren 783 suchen 783 Threadsicherheit 760, 778 Typisierung 760 Warteschlangen (Queue) 773 – Eigenschaften 773 – Methoden 773, 774, 775 Wörterbücher (Map) 776 – Eigenschaften 777 – Hashing 777
Index
D ■■■■■■■■■■ Dämonen 802 dangling else 193 DatabaseMetaData 947 DataFlavor 557 DatagramChannel 878, 998 DatagramPacket 984 DatagramSocket 984 nichtblockierende I/O 998 DataInputStream 849 DataLine 707 DataOutputStream 849 Date 742 Date (SQL) 956 DateFormat 745, 914 Dateien anlegen 831 aus Verzeichnis auflisten 832 auswählen 561 Bilddateien 604 Dateifilter 562, 834 Daten zurückschreiben 849 INI-Dateien 748 Klangdateien (Sound) 704 kopieren 841, 886 lesen 840 löschen 831 Memory Mapping 888 Namen 829 Öffnen-Dialog 561 portierfähiges Speichern 849 Pufferung 842 schreiben 840 sperren 891 verwalten 829 Verzeichnispfade 829 wahlfreier Zugriff 843 XML-Dokumente 1111 Datenbanken anlegen 928 Applets 933 Blob-Daten 957 Datensätze 922 DBMS 922 Fehlerbehandlung 960 Grundlagen 921 InterBase 934 Java-DB 943 JDBC 923 MySQL 923 MySQL-Datentypen 925, 955 ODBC 923 RDBMS 922
1191
Index
– Ladefaktor 777 – Methoden 777, 778 – Rehashing 777 contains() (Collection) 762 contains() (Component) 477 contains() (List) 764 contains() (Map) 778 contains() (Set) 771 contentEquals() (string) 186 ContentPane 510 continue 208, 211 Controller (MVC-Architektur) 508 convertRowIndexToModel () (JTable) 686 convertRowIndexToView () (JTable) 686 copy() (Collections) 787 copy() (JTextComponent) 620 copyArea() (Graphics) 580 copyOf() (Arrays) 278 copyOfRange() (Arrays) 279 copySign() (Math) 164 CORBA 1017 cos() (Math) 163 cosh() (Math) 163 countTokens() (StringTokenizer) 736 create() (Graphics) 580 createCDATASection () (Document) 1123 createComment () (Document) 1123 createElement() (Document) 1123 createGlue() (Box) 514 createGraphics() (Graphics2D) 606 createGraphics() (SplashScreen) 691 createHorizontalBox() (Box) 514 createHorizontalGlue() (Box) 514 createHorizontalStrut() (Box) 514 createNewFile() (File) 832 createPrintJob() (PrintService) 657 createRegistry() (LocateRegistry) 1039 createRigidArea() (Box) 514 createStatement() (Connection) 935, 939 createStatement() (Socket) 983 createTextNode () (Document) 1123 createVerticalBox() (Box) 514 createVerticalGlue() (Box) 514 createVerticalStrut() (Box) 514 Cross-Compiler 80 Cross-Kompilierung 84, 1042 currentThread() (Thread) 803 currentTimeMillis() (System) 215, 752 curveTo() (GeneralPath) 601 cut() (JTextComponent) 620
Index
relationale 921 SQL 923 – Aggregatfunktionen 927 – Datensatz ändern 926 – Datensatz einfügen 926 – Datensatz löschen 926 – Datensatz suchen 926 – Datentypen 925 – Tabellen anlegen 924 SQL/Java-Datentypzuordnung 955, 956 Datenbankprogrammierung Batchläufe 953 Daten – auslesen 937 – durchgehen 938 Ergebnismenge 937 Java-DB 943 Metadaten ermitteln 947 Schreiboperationen 939 Scrolling 939 SQL-Abfragen 935 Transaktionen 949 Treiber laden 932 Type Mapping 955 Verbindung – aufbauen 932 – schließen 935 vorbereitete Abfragen 951 Zugriff über Applets 962 Datenkonvertierung 883 Datenmodell 111 Datentransfer 886 Datentypen 53, 113, 1174 Binärkodierung 118 BLOB (JDBC) 957 Boolescher Typ (boolean) 136, 184 CLOB (JDBC) 957 elementare 114, 125 – boolean 136 – byte 125 – char 125, 133 – double 128 – float 128 – int 125 – long 125 – short 125 – Übersicht 114, 1174 – Wrapper-Klassen 145 Gleitkommatypen 128 – Division 156 – Exponent 130 – Exponentialschreibweise 124 – Infinity 130 – isInfinity() 131
1192
– isNaN() 131 – Mantisse 129 – NaN 130 – Rundungsfehler 132, 330 – signifikante Stellen 131 – Speicherbelegung 129 Integer-Typen 125 – Division 156 – Speicherbelegung 126 – Über- und Unterlauf 127, 328 Java Generics 399 Klassen 217 komplexe 115 – Arrays 116 – Aufzählungen 116 – Klassen 115 – Schnittstellen 116 Literale 123 MySQL 925, 955 Operationen 118 parametrisierte 399 Referenztypen 116 – Arrays 271 – Aufzählungen (enum) 291 – Klassen 217 – Objekterzeugung 116 – Schnittstellen 419 – Vergleiche 117 – Zuweisung 117 Speicherbedarf 118 SQL/Java 955 SQL/Java-Zuordnung 955, 956 Standardwerte 225 String 137 typgebundene Informationen 118 Übersicht 113 vordefinierte 118 Werttypen 116 – Vergleiche 184 – Zuweisung 116 Zeichentyp (char) 133 – Escape-Sequenzen 135 – Speicherbelegung 133 – Unicode 133 Datum 742 DBMS 922 Deadlocks 808 Debuggen 1161 Debugger 1161 decode() (Charset) 884 decode() (URLDecoder) 1004 default 195 DefaultHandler 1113 DefaultMutableTreeNode 678
Index
DocFlavor 655 doClick() (AbstractButton) 531 Document 619, 1118 DocumentBuilder 1118 DocumentListener 632 doInBackground() (SwingWorker) 823 Dokumentationen 1160 Dokumenttyp-Deklarationen (XML) 1107, 1108 DOM (XML) 1117 Domain-Namen 972 done() (SwingWorker) 823 double 128, 145 Double Buffering 590 ausschalten 591 AWT 591 Swing 590 do-while 202 DownCast 384 DR DOS 38 Drag&Drop 697 Datei-Drag 700 Drag aktivieren 698 Drop konfigurieren 698 für Labels 699 Grundlagen 697 Swing-Komponenten 697, 698 TransferHandler – benutzen 699 – implementieren 700 Transferhandler 697 drain() (SourceDataLine) 710 draw() (Graphics2D) 599 draw3DRect() (Graphics) 578 drawArc() (Graphics) 577 drawBytes() (Graphics) 576 drawChars() (Graphics) 576 drawImage() (Graphics) 605 drawLine() (Graphics) 577 drawOval() (Graphics) 577 drawPolygon() (Graphics) 577 drawPolyline() (Graphics) 577 drawRect() (Graphics) 578 drawRoundRect() (Graphics) 578 drawString() (Graphics) 575, 576 Drehfelder 526 DriverManager 933, 934 Drucken 647 aus Datei 666 beenden 650 Bilder 661 DocFlavor 655 Druckauftrag 647, 652 Druckdaten aufbereiten 648, 650 Druckdialoge, javaspezifische 654
1193
Index
DefaultStyledDocument 619 DefaultTreeModel 678 DefaultTreeSelectionModel 681 Definition 120 Arrays 271 Aufzählungen 292 Klassen 217 mehrdimensionale Arrays 285 Methoden 230 Pakete 303 Schnittstellen 423 Definitive Assignment 122, 315 Deinstallation, JDK 85 Deklaration 54 Felder 224 Konstanten 124 Variablen 119 versus Definition 120 Dekrement 157 delete() (File) 832 delete() (StringBuffer) 735 delete() (StringBuilder) 735 deleteRow() (ResultSet) 942 deprecation 1146 Deque 765, 773 Derby 923 Deserialisierung 858 destroy() (Applet) 1043 destroy() (Process) 751 Dialogfenster 559 eigene Dialoge 565 – abfragen 568 – anzeigen 568 – Basisklasse 565 – instanziieren 568 – Konstruktor 568 Login-Dialoge 624 Modalität 559, 568, 569 Standarddialoge 559 – Bestätigungsdialoge 560 – Datei öffnen 561 – Eingabedialoge 560 – Farben auswählen 564 – JOptionPane 559 – Meldungsdialoge 560 – Modalität 559 Digital Research 38 Digitale Unterschriften 1090 disjoint() (Collections) 787 dispose() 267 dispose() (Graphics) 580, 589 Divide-and-Conquer 67 Division 156 do 202
Index
Druckdialoge, native 654 Druckdienste 655 Drucker auswählen 648, 655 Druckparameter 648, 658 getPrintable() (JTextComponent) 649, 660 Grundlagen 647 PageFormat 650 print() (JTextComponent) 649, 660 print() (Printable) 648, 650 Printable 648, 650 PrinterJob 647, 652 Randeinstellungen berücksichtigen 651 Skalierung 666 Standarddrucker 655 Text 649, 660, 667 Duke 36 DWords 1170
E ■■■■■■■■■■ E (Basis des natürl. Logarithmus) (Math) 162 Echozeichen 527 Eclipse 83 Ein- und Ausgabe Console 874 Dateien 829 Dateien siehe Dateien Deserialisierung von Objekten 858 Formatierungsstrings 868 Formatter 868 in Dateien 840 in den Speicher 850 in Textdateien suchen 867 Klassenhierarchie 837 Konsolenanwendungen 94, 853, 874 New Java I/O 877 parsen 855 portierfähiges Speichern 849 printf() (Console) 96 printf() (System.out) 95, 869 Pufferung 842 Reader/Writer-Klassenhierarchie 838 readLine() (Console) 97, 877 Scanner 100, 863 Serialisierung von Objekten 857 Standardein- und -ausgabe 853 Stream-Konzept 837 Tokens 855 über Sockets (blockierend) 974 über Sockets (nicht blockierend) 993 Umlaute 94
1194
Verzeichnisse 829 Verzeichnisse siehe Verzeichnisse von Tastatur lesen 866 wahlfreier Dateizugriff 843 Zeichenkodierung 846, 884 Einbettung siehe Komposition Eingabedialoge 560 Eingaben abstrakte Basisklasse (InputStream) 837 aus Dateien lesen 840 byteorientiertes Lesen 837 Daten zurückschreiben 849 java.io.InputStream 839 New Java I/O 877 parsen 855 Pufferung 842 Reader 840 Standardeingabe 853 von Tastatur 97 – BufferedReader 100 – InputStreamReader 100 – readLine() 100 Zeichenkodierung 846, 884 zeichenorientiertes Lesen 838 Element (XML) 1104 element() (Queue) 774 Ellipse2D 600 else 191 embed-Tag 1047 empty() (Stack) 766 encode() (Charset) 884 encode() (URLEncoder) 1004 end() (Matcher) 739 endDocument () (DefaultHandler) 1113 endElement () (DefaultHandler) 1113 Endlosschleifen, gewollte 209 Endpoint (Klasse) 1133 endsWith() (String) 170 Entscheidungen 181 Entwicklungsumgebungen Buch-CD 83 Download-Adressen 83 enum 291 enum siehe Aufzählungen enumerate() (ThreadGroup) 819 Enumeration 782 EnumMap 778 EnumSet 770 eolIsSignificant() (StreamTokenizer) 856 equals() (Arrays) 279, 626 equals() (Aufzählungen) 295 equals() (Comparator) 785 equals() (Locale) 908
Index
exitValue() (Process) 751 exp() (Math) 163 expm1() (Math) 163 Exponent 130 exportObject() (UnicastRemoteObject) 1040 Externalizable 862
F ■■■■■■■■■■ Fakultät 323 Farben 578 Alpha-Kanal 580 auswählen 564 Color-Klasse 578 Farben kreieren 579 Hintergrundfarbe 580 RGB-Farben 579 Standarddialog 564 SystemColor-Klasse 579 Transparenz 580 vordefinierte Farben 578 fatalError () (DefaultHandler) 1114 Fehler Laufzeitfehler 431 logische Fehler 431 Nebeneffekte 179, 214 Syntaxfehler 431 Fehlerbehandlung 431 durch Exceptions 435 JDBC 960 Rückgabewerte von Methoden 434 System.err 432 Übersicht 431 Fehlersuche 1161 Felder 65, 224 Gültigkeitsbereich 225 Initialisierung 225 konstante Felder 226 Nur-Lesen 268 Nur-Schreiben 268 Objekte 226 statische Felder 228 Verdeckung 308 Zugriff auf verdeckte Felder 373 Fenster 507 Basisklasse – AWT 474 – Swing 509 Designs 519 Dialoge 559 in Fenster zeichnen 571
1195
Index
equals() (Object) 173, 461 überschreiben 461 equals() (String) 170, 185 equalsIgnoreCase() (String) 185 Ereignisempfänger 494 Ereignisobjekte 494 Ereignisquellen 494 Ereignisse 494 Adapterklassen 501 Ereignisobjekte 494 Ereignisparameter 502 Ereignisquellen 494 Listener 494 Modelle 495 Paint 585 err 432, 853 Error 441 error () (DefaultHandler) 1114 Escape-Sequenzen Übersicht 135 Unicode 134 Eulersche Zahl 124 Event 553 EventObject 494, 502 Exception-Behandlung 436 Aufbau 436 catch-Blöcke 438 Exceptions – abfangen 438 – auslösen 444 – verketten 445 – weiterleiten 443 finally 448 Stack-Auflösung 447 throws-Deklaration 443 try-Blöcke 438 veränderter Programmfluß 447 Exceptions 431, 441 abfangen 438 auslösen 444 Behandlungszwang 444 eigene 446 Exception-Hierarchie 440 verketten 445 weiterleiten 443 exec() (Runtime) 751 execute() (PreparedStatement) 952 execute() (SwingWorker) 823 executeQuery() (PreparedStatement) 952 executeQuery() (Statement) 935 executeUpdate() (PreparedStatement) 952 executeUpdate() (Statement) 935 exit() (System) 435, 705, 752
Index
Layout-Manager 486 Look&Feel 519 – ändern 519 – UIManager 520 schließen 474, 510 Standardaktionen 510 Titel festlegen 474 untergeordnete (Kindfenster) 676 File 604, 636, 829 FileChannel 878, 892 FileFilter 562, 835 FileInputStream 840 FileLock 892 FilenameFilter 834 FileOutputStream 840 FileReader 636, 840 FileWriter 636, 840 fill() (Arrays) 278 fill() (Collections) 787 fill() (Graphics2D) 599 fill3DRect() (Graphics) 578 fillArc() (Graphics) 577 fillOval() (Graphics) 577 fillPolygon() (Graphics) 577 fillRect() (Graphics) 578 fillRoundRect() (Graphics) 578 final (für Felder) 226 final (für Klassen) 219, 358 final (für Methoden) 390 final (für Parameter) 239 finalize() (Graphics) 580 finalize() (Object) 173, 265 finally 448 find() (Matcher) 739, 741 findInLine() (Scanner) 865 findWithinHorizon() (Scanner) 865 fireTableDataChanged() (AbstractTableModel) 685 Firewalls 989, 993 first() (ResultSet) 940 flip() (ByteBuffer) 880, 882 Float 145 float 128 floor() (Math) 163 FlowLayout 487 flush() (Console) 875 flush() (OutputStream) 840 flush() (ServerSocket) 980 FocusAdapter 500 FocusEvent 499 focusGained() (FocusListener) 500 FocusListener 500 focusLost() (FocusListener) 500 Fokus 644 Font 575
1196
FontMetrics 583 Fonts 575, 637, 638 Abmaße 583 Font-Objekte – anpassen 638 – erzeugen 641 – kopieren 638 installierte Schriftarten ermitteln 641 Namen 575 for 203 force() (MappedByteBuffer) 890 for-each 204 format() (Console) 875 format() (DateFormat) 745 format() (Formatter) 870 format() (PrintStream) 869 format() (PrintWriter) 869 format() (String) 870 Formatierungsstrings 868 Formatter 870 forName() (Charset) 884 forName() (Class) 932 Fortran 52 Fortschrittsanzeigen 526, 543 FP-strict 332 Frame 474 freeMemory() (Runtime) 750 frequency() (Collections) 787 Füllmuster 598 Funktionen (kein Java) 58
G ■■■■■■■■■■ Gateways 967 gc() (System) 752 Gebietsschema 907 GeneralPath 601 Generics 399 Arrays 407 extends (für Platzhalter) 408 extends (für Wildcards) 407 Platzhalter 401, 408 super (für Wildcards) 408 Syntax 401 – parametrisierte Klassen 401 – parametrisierte Methoden 402 Typisierung 401 Überladung 412 Umsetzung 412 und Casting 414 und getClass() 414 und instanceof 414
Index
getClass() (Object) 173, 398 getClickCount() (MouseEvent) 503 getClob() (ResultSet) 939, 958 getCodeBase() (Applet) 1053 getColor() (Graphics) 578 getColor() (JColorChooser) 564 getColumnClass() (AbstractTableModel) 685 getColumnCount() (AbstractTableModel) 684 getColumnCount() (ResultSetMetaData) 949 getColumnName() (AbstractTableModel) 684 getColumnName() (ResultSetMetaData) 949 getColumnType() (ResultSetMetaData) 949 getColumnTypeName() (ResultSetMetaData) 949 getComponentAt() (Component) 477 getConnection() (DriverManager) 934 getContentPane() (JFrame) 511 getContentPane() (JInternalFrame) 677 getContents() (Clipboard) 556 getCountry() (Locale) 908 getCrossPlatformLookAndFeelClassName() (UIManager) 520 getCurrencyInstance() (NumberFormat) 915 getCursor() (Component) 477 getData() (Clipboard) 556 getData() (DatagramPacket) 985, 986 getDatabaseProductName() (DatabaseMetaData) 948 getDate() (ResultSet) 938 getDateInstance() (DateFormat) 914 getDefault() (Locale) 908 getDescription() (FileFilter) 563 getDisplayCountry() (Locale) 908 getDocument() (JTextComponent) 620 getDouble() (ResultSet) 938 getDriverVersion() (DatabaseMetaData) 948 getEncoding() (InputStreamReader) 847 getEnv() (System) 752 getErrorCode() (SQLException) 960 getExponent() (Math) 164 getFile() (URL) 1002 getFilePointer() (RandomAccessFile) 843 getFirstChild () (Node) 1120 getFirstIndex() (ListSelectionEvent) 688 getFloat() (ResultSet) 938 getFont() (Component) 477 getFont() (Graphics) 575 getFontMetrics() (Graphics) 576 getForeground() (Component) 477 getFormat() (AudioInputStream) 710 getFreeSpace() (File) 835 getGraphics() (Component) 477, 573 getHeaderField() (HttpURLConnection) 1011 getHeight() (JComponent) 527 getHost() (URL) 1002 getHostAddress() (InetAddress) 975 getHostName() (InetAddress) 974
1197
Index
untypisierte Verwendung 413 Vererbung 409 Wildcard-Typ 405, 407 Gerätekontexte 572 beziehen 572 erzeugen 573 löschen 580, 589 Geschichte, Javas 35, 337 get() (ByteBuffer) 882 get() (Calendar) 744 get() (List) 765 get() (Map) 777 get() (SwingWorker) 823 getAbsolutePath() (File) 830 getActionCommand() (AbstractButton) 531 getActionCommand() (ActionEvent) 497, 502 getActions() (JTextComponent) 623 getAddress() (DatagramPacket) 985 getAddress() (InetAddress) 975 getAlignment() (Label) 482 getAsciiStream() (Clob) 957 getAttribute() (Element) 1123 getAttributes() (Node) 1119 getAudioClip() (Applet) 705, 1053 getAudioInputStream() (AudioSystem) 710 getAvailableDataFlavors() (Clipboard) 556 getAvailableLocales() (DateFormat) 914 getAvailableLocales() (Lokale) 908, 909 getAvailableLocales() (NumberFormat) 914 getBackground() (Component) 477 getBinaryStream() (Blob) 957 getBlob() (ResultSet) 939, 958 getBoolean() (ResultSet) 938 getBorder() (JComponent) 527 getBounds() (Component) 477 getBuffer() (StringWriter) 853 getBundle() (ResourceBundle) 897, 902, 913 getButton() (MouseEvent) 503 getButtonCount() (ButtonGroup) 534 getByAddress() (InetAddress) 974 getByName() (InetAddress) 974, 975 getBytes() (Blob) 957 getBytes() (String) 170 getCanonicalPath() (File) 830 getCaret() (JTextComponent) 621 getCaretColor() (JTextComponent) 621 getCaretPosition() (JTextComponent) 621 getCatalogs() (DatabaseMetaData) 948 getCause() (Throwable) 441 getChannel() (RandomAccessFile) 843, 880 getChannel() (ServerSocket) 983 getCharacterStream() (Clob) 957 getChildCount() (DefaultMutableTreeNode) 679 getChildNodes () (Node) 1120
Index
getID() (AWTEvent) 502 getImage() (Applet) 1053 getImage() (TrayIcon) 694 getImageableX() (PageFormat) 651 getImageableY() (PageFormat) 651 getInputStream() (Process) 751 getInputStream() (Socket) 984 getInputStream() (URLConnection) 1005 getInstance() (Collator) 731, 917 getInt() (ResultSet) 938 getISOCountries() (Locale) 911 getISOCountry() (Locale) 908 getISOLanguages() (Locale) 911 getItem() (ItemEvent) 502 getItemCount() (JComboBox) 543 getItemSelectable() (ItemEvent) 551 getKeyChar() (KeyEvent) 503 getKeyCode() (KeyEvent) 503 getKeyStroke() (Keystroke) 553 getKeyText() (KeyEvent) 503 getLabel() (Button) 483 getLabel() (Checkbox) 484 getLastChild () (Node) 1120 getLastIndex() (ListSelectionEvent) 688 getLeading() (FontMetrics) 583 getLength() (Attributes) 1114 getLine() (AudioSystem) 710 getLineCount() (JTextArea) 628 getLineEndOffset() (JTextArea) 628 getLineOfOffset() (JTextArea) 628 getLineStartOffset() (JTextArea) 628 getLineWrap() (JTextArea) 628 getLocalHost() (InetAddress) 974 getLocation() (Component) 477 getMaximum() (JProgressBar) 544 getMaximumSize() (JComponent) 512, 527 getMaxPriority() (Thread) 816 getMaxPriority() (ThreadGroup) 819 getMessage() (Throwable) 441 getMetaData() (Connection) 947 getMinimum() (JProgressBar) 544 getMinimumSize() (JComponent) 512, 527 getModel() (AbstractButton) 618 getName() (Clipboard) 556 getName() (Component) 477 getName() (Thread) 803 getNextException() (SQLException) 960 getNextSibling () (Node) 1120 getNodeName() (Node) 1119 getNodeType() (Node) 1119 getNodeValue() (Node) 1119 getNumberInstance() (NumberFormat) 914 getOppositeComponent() (FocusEvent) 502 getOutputStream() (Process) 751
1198
getOutputStream() (Socket) 984 getOutputStream() (URLConnection) 1005 getParameter() (Applet) 1055 getParent() (Component) 477 getParent() (DefaultMutableTreeNode) 679 getParent() (File) 830 getParent() (ThreadGroup) 819 getParentNode () (Node) 1120 getPassword() (JPasswordField) 626 getPath() (File) 830 getPath() (URL) 1002 getPercentInstance() (NumberFormat) 915 getPort() (DatagramPacket) 985 getPort() (Socket) 983 getPort() (URL) 1002 getPreferredSize() (JComponent) 512, 528 getPreviousSibling () (Node) 1120 getPrintable() (JTextComponent) 649, 660 getPriority() (Thread) 803 getProperties() (Properties) 748 getProperties() (System) 752 getProperty() (System) 752, 846 getProtocol() (URL) 1002 getQName() (Attributes) 1114 getRef() (URL) 1002 getRegistry() (LocateRegistry) 1039 getResource() (Class) 432, 901 getResponseMessage() (HttpURLConnection) 1011 getRootPane() (JComponent) 528 getRow() (ResultSet) 940 getRowCount() (AbstractTableModel) 684 getRows() (JTextArea) 628 getRuntime() (Runtime) 750 getSecurityManager() (System) 1086 getSecurityManager() (System) 752 getSelectedCheckbox() (CheckboxGroup) 485 getSelectedColumn() (JTable) 687 getSelectedColumns() (JTable) 687 getSelectedFile() (JFileChooser) 562 getSelectedIndex() (JComboBox) 543 getSelectedIndex() (JList) 542 getSelectedIndices() (JList) 542 getSelectedItem() (JComboBox) 543 getSelectedRow() (JTable) 686 getSelectedRows() (JTable) 686 getSelectedText() (JTextComponent) 620, 622 getSelectedTextColor() (JTextComponent) 622 getSelectedValue() (JList) 542 getSelectedValues() (JList) 542 getSelectionEnd() (JTextComponent) 622 getSelectionStart() (JTextComponent) 622 getSize() (Component) 477 getSource() (ActionEvent) 524 getSource() (EventObject) 480, 502
Index
Grafik 571 aktualisieren 587 Animationen 590 AWT 581 Bilder 604, 605 – anzeigen 605 – bearbeiten 606 – BufferedImage-Klasse 604 – drucken 661 – Image-Klasse 604 – laden 604 – speichern 604 Canvas 573 Clipping 611 – Device-Clip 612 – Effekte 613 – effektives Neuzeichnen 612 – User-Clip 612 computererzeugte Grafiken 591 Double Buffering 590 Farben 578 Figuren 576 Flackern 590 Gerätekontexte 572 – beziehen 572 – erzeugen 573 – löschen 580, 589 getGraphcis() 573 Graphics2D-Klasse 596 Graphics-Klasse 572 Grundlagen 571 Java2D 595 – drehen 602 – Füllmuster 598 – geometrische Figuren 599 – Gradientenfüllung 599 – Graphics-Objekt umwandeln 596 – Linienstile 597 – skalieren 603 – Strichstärke 597 – verschieben 602 – Zeichenmethoden 599 JPanel 574 Linien 576 mit Maus zeichnen 588 Neuzeichnen 585 paint() 572, 585 paintComponent() 573, 574, 585 Paint-Ereignis 585 rekonstruieren 585 Schriftarten 575 Schriften 575 Strichstärke 597 Swing 583
1199
Index
getSplashScreen() (SplashScreen) 691 getState() (Checkbox) 484 getState() (Thread) 794 getStateChange() (ItemEvent) 551 getString() (ResourceBundle) 899 getString() (ResultSet) 938 getSubString() (Clob) 958 getSystemClipboard() (Toolkit) 557 getSystemLookAndFeelClassName() (UIManager) 520 getSystemTray() (SystemTray) 694 getTabComponentAt() (JTabbedPane) 674 getTabSize() (JTextArea) 628 getText() (Document) 620 getText() (JTextComponent) 620 getText() (Label) 482 getTimeInstance() (DateFormat) 914 getToolTip() (JComponent) 528 getTopLevelAncestor() (JComponent) 528 getTotalSpace() (File) 835 getTransferData() (StringSelection) 556 getTransferDataFlavors() (StringSelection) 556 getUsableSpace() (File) 835 getValue() (Attributes) 1114 getValueAt() (AbstractTableModel) 684 getValueAt() (JTable) 687 getViewPort() (JScrollPane) 624, 630 getWidth() (JComponent) 528 getWrapStyleWord() (JTextArea) 628 getX() (JComponent) 528 getX() (MouseEvent) 503 getY() (JComponent) 528 getY() (MouseEvent) 503 GlassPane 510 Gleitkommatypen 128 Division 156 Exponent 130 Exponentialschreibweise 124 Infinity 130 isInfinity() 131 isNaN() 131 Mantisse 129 Nachkommastellen in Ausgabe 452, 872 NaN 130 Rundungsfehler 132, 330 signifikante Stellen 131 Speicherbelegung 129 Gleitkommazahlen 53 Datentypen 128, 325 spezielle Funktionen 164 Glue 514 GMT 741 grabFocus() (JComponent) 528, 644 Gradientenfüllung 599 GradientPaint 599
Index
Text 575 Überschreibmodus 581 Zeichenmethoden 574 – für Bilder 605 – für Clipping 614 – für Farben 578 – für Figuren 576 – für Linien 576 – für Text 575 – für Überschreibmodus 581 Grafische Benutzeroberflächen (GUI) 471 Graphics 572, 650 Graphics2D 596 GregorianCalendar 744 grep 740 GridBagConstraints 492 GridBagLayout 492 GridBagLayout-Manager 492 GridLayout 488 GridLayout-Manager 488 Group 515 GroupLayout 515 GroupLayout.Alignment 516 GroupLayout.ParallelGroup 515 GroupLayout.SequentialGroup 515 GroupLayout-Manager 515 Grundflächen 485 Grundgerüste Applets 1041 GUI-Anwendungen – AWT 473 – Swing 508 Konsolenanwendungen 90 GTK (Look&Feel) 519 GUI-Programmierung 471 AWT 472 Container 473 – AWT 476 – Swing 512, 673 Dialogfenster 559 Drag&Drop 697 Drucken 647 Ereignisse 494 Gerätekontexte 572 Grafik 571 Grundgerüst 473, 508 Komponenten 473 – AWT 476 – Swing 525 Layout-Manager 473, 486 Look&Feel 519 Menüs 545 Multiple Document Interface 676 Paint-Ereignis 585
1200
Passwörter 527 Quickinfo 528 Ressourcen 901 Sound 704 – mit AudioClip 704 – mit der Sound API 706 Startbildschirme (Splash-Screen) 690 Swing 507 Symbolleisten 554 SystemTray 693 Textverarbeitung 617 Gültigkeitsbereiche 122, 305 Anweisungsblock 305 Array 307 Klasse 218, 306 Methoden 306 Pakete 299 Redeklaration 307 Verdeckung 308 Verschachtelung 308 von Array-Elementen 307 von Feldern 225, 306 von lokalen Variablen 305 von Parametern 306
H ■■■■■■■■■■ handleEvent() 502 hasChildNodes () (Node) 1120 Hash-Code 172 hashCode() (Object) 174 überschreiben 464 Hashing 777 HashMap 778 HashPrintRequestAttributeSet 658 HashSet 770 Hashtabellen 623, 776 Hashtabellen siehe Wörterbücher (Map) Hashtable 623, 778 hasMoreTokens() (StringTokenizer) 736 hasNext() (Iterator) 781 hasNext() (ListIterator) 782 hasNext() (Scanner) 865 hasNextXxx() (Scanner) 865 hasPrevious() (ListIterator) 782 Heap 313 Heavyweight-Komponenten 486 Hexadezimalzahlen 127 Hintergrundfarbe 580 hitClip() (Graphics) 614 Hoare, C.A.R. 280 HotJava 37
Index
I ■■■■■■■■■■ IBM 39 IEEEremainder() (Math) 164 if 190 if-Anweisung 190, 193 if-else-Anweisung 191 ignorableWhitespace () (DefaultHandler) 1113 IllegalArgumentException 489 Image 604 ImageIcon 852 ImageIO 604 import 301 importData() (TransferHandler) 702 in 853 indexOf() (List) 765 indexOf() (String) 170 indexOf() (StringBuffer) 734 indexOf() (StringBuilder) 734 indexOfTabComponent() (JTabbedPane) 674 InetAddress 974 InetSocketAddress 976 Infinity 130 Infix-Notation 766 INI-Dateien 748 init() (Applet) 1043, 1044, 1055 Initialisierung Arrays 273 Felder 225 geerbte Elemente 366 Klassen 269 konstante Felder 226 mehrdimensionale Arrays 285 Variablen 121
Inkrement 157 Innere Klassen 319 Instanziierung 321 statische 322 InputMismatchException 864, 866 InputStream 839, 977 InputStreamReader 100, 847 insert() (JTextArea) 627 insert() (StringBuffer) 734 insert() (StringBuilder) 734 insertBefore() (Node) 1124 insertRow() (ResultSet) 942 insertUpdate() (DocumentListener) 632 Installation Java-API-Quelltexte 85 JDBC-Treiber 1167 JDK 84 – Klassenpfad anpassen (CLASSPATH) 88 – Linux 85 – Systempfad erweitern (PATH) 86 – Windows 84 MySQL 1165 instanceof 397 Instanzbildung 70, 222 innere Klassen 321 verhindern 268 Instanzen 65, 222 Instanzvariablen 229, 233 int 125 IntBuffer 878 Integer 53, 100, 145 Integer-Typen 125 Division 156 Speicherbelegung 126 Über- und Unterlauf 127, 328 InterBase 934 interface 423 Interfaces siehe Schnittstellen intern() (String) 140 Internationalisierung 906 Internationalisierung siehe Lokalisierung Internet Explorer 38 Internet-PC 36 Interpreter 46, 80, 1155 Aufruf 93, 1156 Optionen 1156 interrupt() (Thread) 801 interrupt() (ThreadGroup) 819 InterruptedException 802 invalidate() (Component) 477 InvalidClassException 862 invokeLater() (SwingUtilities) 822 invokeLaterAndWait() (SwingUtilities) 822
1201
Index
HtmlConverter 1047 HTTP Ablauf einer HTTP-Kommunikation 1011 Anfrage (REQUEST) 1008 Antwort (RESPONSE) 1009 Header 1010 Protokoll 1007 Sessions 1004 Statusnummern 1179 URIs 1001 URLs 1001 Zugriff über Passwort 1014 Zugriff über Proxy 1014 HTTP-Protokoll 1001 HttpURLConnection 1004, 1011, 1015 hypot() (Math) 163
Index
IOException 439, 441, 830, 837 IP (Internet Protocol) 970 IP-Adressen 970 ipconfig 1026 isAlive() (Thread) 803 isCancelled() (SwingWorker) 823 isCellEditable() (AbstractTableModel) 684, 685 isDaemon() (Thread) 803 isDataFlavorAvailable() (Clipboard) 556 isDataFlavorSupported() (StringSelection) 556 isDataFlavorSupported() (TransferHandler.TransferSupport) 702 isDigit() (Character) 731 isDirectory() (File) 832 isDoubleBuffered() (JComponent) 528 isEditable() (JTextComponent) 620 isEmpty() (Collection) 762 isEmpty() (List) 765 isEmpty() (Map) 778 isEmpty() (String) 185 isEnabled() (Component) 477 isFile() (File) 832 isInfinity() (Gleitkommatypen) 131 isLeaf() (DefaultMutableTreeNode) 679 isLetter() (Character) 731 isLightweightComponent() (JComponent) 528 isLoaded() (MappedByteBuffer) 890 isLowerCase() (Character) 731 isNaN() (Gleitkommatypen) 131 ISO-OSI-Referenzmodell 968 isOpen() (FileChannel) 881 isPopupTrigger() (MouseEvent) 503 isSelected() (AbstractButton) 531 isSelectedIndex() (JList) 542 isShared() (FileLock) 892 isSupported() (SystemTray) 694 isUpperCase() (Character) 731 isVisible() (Component) 477 isWhitespace() (Character) 731 ItemEvent 499, 532, 539 ItemListener 500, 550 ItemListener (für JCheckBox) 539 ItemListener (für JComboBox) 542 ItemListener (für JToggleButton) 532 itemStateChanged() (ItemListener) 500, 532, 539, 542, 550 Iterator 758, 780, 781 erzeugen 781 verwenden 781 iterator() (Collection) 762 iterator() (List) 765 iterator() (Set) 771
1202
J ■■■■■■■■■■ James Gosling 35, 37 JApplet 1041 JAR-Archive 1157 Applets 1060 einsehen 1158 entpacken 1158 erstellen 1158 signieren 1093 Startbildschirme 691 jarsigner 1094 Java API siehe API Applets 79, 1041 benutzerdefinierte Classloader 1084 Bootstrap-Classloader 1084 Bytecode 82 Classloader 932, 1031, 1084 Compiler 82 Datenmodell 111 Datentypen 1174 definitive assignment 122, 315 Development Kit 83 Duke 36 Entwicklungsumgebungen – Buch-CD 83 – Download-Adressen 83 Geschichte 35, 337 – Browser-Krieg 40 – Green-Projekt 36 – Klagen gegen Microsoft 38 – Oak 36 Interpreter 80 jar 1157 java 1155 javac 1139 javadoc 1160 jdb 1161 JNI (Java Native Interface) 1063 Laufzeitumgebung (JRE) 81 Manifest-Dateien 1159 Mehrfachvererbung 420 Methodenaufrufe 389 Neuerungen 30 Pakete 102, 299 Plattformunabhängigkeit 80 Plug-in 81, 962, 1042, 1048 Portierbarkeit 79 Programme debuggen 1161 Programmerstellung 79 Quelltextdateien, Benennung 1143 Robustheit 81
Index
Installation 83, 84 – Klassenpfad anpassen (CLASSPATH) 88 – Linux 85 – Systempfad erweitern (PATH) 86 – Windows 84 jarsigner 1094 java 80 javac 82 javah 1065 javap 1079 keytool 1093 JDOM (XML) 1125 JEditorPane 527, 619 JFileChooser 561 JFormattedTextField 527 JFrame 509 JInternalFrame 676 JITter 82 JLabel 526, 529, 699 JLayeredPane 510, 526 JList 526, 540 JMenu 525, 546 JMenuBar 526, 546 JMenuItem 525, 546 JMPR 1017 JNI (Java Native Interface) 1063 C/C++-Code erstellen 1066 C/C++-Headerdatei erzeugen 1065 Datentypen 1068 dynamische Bibliothek erstellen 1066 javah 1065 javap 1079 Mapping 1068 native Methoden – deklarieren 1064 – DLL laden 1065 – Java-Methoden aufrufen 1077 Parameter 1067 Rückgabewerte 1067 Stringfunktionen 1071 Zugriffsmethoden für Java-Variablen und Methoden 1079, 1080 John D. Rockefeller 39 join() (Thread) 803 joinGroup() (Multicast) 990 JOptionPane 526, 559 JPanel 526, 574 JPasswordField 527, 624 JPopupMenu 526, 549 JProgressBar 526, 543, 544 JRadioButton 526, 539 JRadioButtonMenuItem 525, 548 JRE 81 JRootPane 510
1203
Index
Schlüsselwörter 1173 Serialisierung 859 Sicherheit 1083 – Spyware 1083 – Zugriffsrechte 1088 Sicherheitsmanager 1086 Speicherbereinigung 265 SQL/Java-Datentypzuordnung 955, 956 Standardbibliothek 80, 102 Stilkonventionen 105 Tools 1139 Typumwandlung 142 Virtual Machine 41, 82, 389, 1042 Website 83 java (Java-Interpreter) 80 Java2D 595 drehen 602 Füllmuster 598 geometrische Figuren 599 Gradientenfüllung 599 Graphics-Objekt umwandeln 596 Linienstile 597 skalieren 603 Strichstärke 597 verschieben 602 Zeichenmethoden 599 javac (Java-Compiler) 82, 1085 Java-DB 923, 943 javah (JNI) 1065 Java-Konsole 1048 javap (JNI) 1079 Java-Plug-In-Dialogfeld 1048 Java-SDK siehe JDK JavaStation 36 Javedi 628 JAXP (XML) 1112 JBuilder 83 JButton 525, 531 JCheckBox 526, 618 JCheckBoxMenuItem 525, 548 JCheckButton 536 JColorChooser 564 JComboBox 526, 540 JComponent 525, 527 JDBC (Java Database Connectivity) 923 JDBC-Treiber 1167 SQL/Java-Datentypzuordnung 955, 956 JDesktopPane 526, 676 JDialog 565 JDK 83 ältere Versionen 84 appletviewer 1046 Deinstallation 85 htmlconverter 1047
Index
JScrollBar 526 JScrollPane 526, 623, 629 JSlider 526, 565 JSpinner 526 JSplitPane 526, 673 JTabbedPane 526, 673 JTable 527, 682 JTextArea 527, 627, 700 JTextComponent 527, 617, 649, 660 JTextField 527, 624 JTextPane 527, 619 JToggleButton 526, 532, 637 JToolBar 527, 554 JToolTip 527 JTree 527, 677
K ■■■■■■■■■■ Kalender 742 gregorianischer 742 julianischer 742 Kanäle (NIO) 877 Kapselung 217 KDE 48 KEdit 91 Kellerautomat 334 Kennwörter GUI-Anwendungen 527 Konsolenanwendungen 876 KeyAdapter 500 KeyEvent 499, 554 KeyListener 500 Keymap 623 keyPressed() (KeyListener) 500 keyReleased() (KeyListener) 500 keySet() (Map) 778 Keystroke 553 keytool 1093 keyTyped () (KeyListener) 500 Klammern (in Ausdrücken) 175, 334 Klassen 62, 217 abgeleitete 355 abstrakte 415, 418 anonyme 322, 430, 497 auf Pakete verteilen 352 auf Quelldateien verteilen 351 Basisklassen 355 Dateinamen 91 Definition 70, 217 – Modifizierer 219 – Zugriffsspezifizierer 219, 256
1204
Design 258 – abstrakte Basisklassen 415 – Black Box 260 – CASE-Tools 259 – Einfachheit 260 – Elementauswahl 259 – Factory-Methoden 268 – Funktionensammlungen 268 – get/set-Methoden 262 – Information Hiding 260 – Instanzbildung verhindern 268 – Konstruktor 264 – Methoden 267 – öffentliche Schnittstelle 259 – private Konstruktoren 268 – Sicherheit 261 – UML 259, 344 – Vererbung 377 – Ziele 258 – Zugriffsrechte 259 Elemente 218 – Felder 224 – Konstruktoren 245, 251 – Methoden 229 – Modifizierer 220 – statische Felder 228 – statische Methoden 245 – Zugriffsspezifizierer 220, 256 Erzeugung 269, 311 Felder 224 finalize() 264, 265 get/set-Methoden 262 importieren 301 innere 319 – Instanziierung 321 – static-Deklaration 322 Instanzbildung 70, 222 Instanzen 222 Instanzvariablen 229, 233 Kapselung 217 Klassenvariablen 229, 233 Komposition 379 Konstruktoren 72, 245, 251 lokale 322 Methoden – statische Methoden 245 – Überladung 247 Objekte 223 – auflösen 264 – kopieren 453 – vergleichen 460 Objekterzeugung 116 Pakete 299
Index
verschachteln 493 Wechselschalter 532 Komposition 379 Konkatenation 168 Konsole 48 Konsolenanwendungen 89 Ausgabe 94 Befehlszeilenargumente 283 Eingabe 97 erstellen 91 Grundgerüst 90 Menüs 196 OEM-Zeichensatz 134, 875 Passwörter 876 Standardausgabe umlenken 853 Umlaute 875 Unicode-Zeichen ausgeben 134, 875 Konstanten 226 Drucken 650 E (Basis des natürl. Logarithmus) (Math) 162 Fensterkonstanten 510, 511 importieren 302 Literale 123 PI (Math) 162 SwingConstants 529 symbolische (final) 124 Konstruktoren 72, 251 Basisklassenkonstruktoren 366 – Argumente übergeben 369 – Aufruf 368 – Auswahl 370 – Standardkonstruktor 367 Ersatzkonstruktor 252 private 245, 268 Standardkonstruktor 252 Vererbung 366 Kontextmenüs 549 Kontrollkästchen 483, 526, 536 Kontrollstrukturen 49, 181, 208 Abbruchbefehle 208, 211, 212 Bedingungen 181, 183 do-while-Schleife 202 for-Schleife 203 if-Anweisung 190 if-else-Verzweigung 191 Schleifen 198 Sprunganweisungen 206 switch-Verzweigung 194 Verzweigungen 190 while-Schleife 198 Kooperatives Scheduling 816 Kopieren Arrays 278 flaches Kopieren 454
1205
Index
parametrisierte 401 Schreibweise 509 static-Blöcke 269 statische Elemente 75 this 242 TopLevel-Klassen 319 Vererbung 76, 355 versus Schnittstellen 422 Wrapper-Klassen 145 Klassenhierarchien 379 Klassenlader 1031, 1084 Klassenpfad 1140 Compiler-Option 1141 Umgebungsvariable setzen 88, 1140 Klassenvariablen 229, 233 Kombinationsfelder 526, 540 Kommentare 47, 91 Anweisungen auskommentieren 107 einzeilige 91 mehrzeilige 91 XML 1107 Kompilierung Applets 1045 javac-Aufrufe 1146 mehreren Quelldateien 304, 353 Programme mit Ressourcen 913 Komponenten 473, 525 Abstände 514 AWT 476 Basisklasse Component 476 Basisklasse JComponent 527 Beschriftungsfelder 529 Drag&Drop-Unterstützung 697, 698 einfügen – AWT 479 – Swing 511 einrichten 478 Entwurfsmuster 508 Fortschrittsanzeige 543 Größe – bevorzugte 512 – maximale 512 – minimale 512 Heavyweight 486 in Container einfügen 479 Kombinationsfelder 540 Kontrollkästchen 536 Lightweight 486 Listenfelder 540 Optionsfelder 539 Quickinfo 528 Schaltflächen 531 Swing 511, 525 Vererbungshierarchie 476
Index
Objekte 453 Strings 138 tiefes Kopieren 458 Kurzinfo 527
L ■■■■■■■■■■ Label 476, 481 LAN (local area network) 967 last() (ResultSet) 940 lastIndexOf() (List) 765 Laufwerke, Belegung 835 Laufzeitmessungen 743, 752 Laufzeittypidentifizierung 396 Laufzeitumgebung (JRE) 81 LayeredPane 510 Layout-Manager 473, 486 Anordnung als Kartenstapel 491 Anordnung in Gruppen 515 Anordnung in Rahmenbereichen 490 Anordnung in Rastern 488, 492 Anordnung in Reihen 487 Anordnung in Zeilen/Spalten 513 Applets 1044 Box 513 BoxLayout 513 FlowLayout 487 freie Anordnung 493 GridBagConstraints 492 GridBagLayout 492 Group 515 GroupLayout 515 Gruppenlayout 515 Komponentengrößen 512 Null-Layout 493 OverlayLayout 513 ScrollPaneLayout 513 SpringLayout 513 Swing 512, 513 ViewportLayout 513 Lebensdauer 310 Array-Elemente 311 Instanzvariablen 310 Klassenvariablen 310 lokale Variablen 310 Objekte 311 Parameter 310 Speicherbereinigung 313 Leere Anweisungen 153, 212 length() (Blob) 957 length() (Clob) 958 length() (File) 832
1206
length() (RandomAccessFile) 843 length() (String) 170 Lightweight-Komponenten 486 limit() (ByteBuffer) 882 Line 707 Line2D 600 LineInputReader 842 LineNumberReader 741 lineTo() (GeneralPath) 601 Linienstile 597 LinkedHashMap 778 LinkedHashSet 770 LinkedList 765, 773 Linker 46 Linux JDK – CLASSPATH-Umgebungsvariable setzen 88 – Installation 85 KEdit 91 vi 91 List 476, 758, 764, 765 list() (File) 832 Listen 763 Eigenschaften 763 java.util.ArrayList 765 java.util.LinkedList 765 java.util.Stack 766 java.util.Vector 765 Methoden 764, 765 Listener 494 Listenfelder 526, 540 listFiles() (File) 832 ListIterator 781 listIterator() (List) 765 listRoots() (File) 832 ListSelectionEvent 688 ListSelectionListener 541, 687, 688 Literale 47, 123 elementarer Datentypen 123 Strings 137 load() (MappedByteBuffer) 890 load() (Properties) 748 loadLibrary() (System) 752, 1065 Locale 907 LocateRegistry 1039 lock() (FileChannel) 892 log() (Math) 163 log10() (Math) 163 log1p() (Math) 163 Login 624 Lokale 907 abfragen 908 ändern 911 erzeugen 909
Index
M ■■■■■■■■■■ Mac (Look&Feel) 519 main() 91 MalformedURLException 705, 1002 Manifest-Dateien 1159 Mantisse 129 Map 758, 777 map() (FileChannel) 889 MappedByteBuffer 889 Marc Andreessen 37 mark() (ByteBuffer) 882 mark() (InputStream) 839 Markierungen (Text) 621
markSupported() (InputStream) 839 match() (Scanner) 865 Matcher 739 matcher() (Pattern) 738 matches() (Matcher) 739 matches() (Pattern) 738 matches() (String) 170 Math 102, 162 Mathematische Funktionen 162 max() (Collections) 787 max() (Math) 163 MDI (Multiple Document Interface) 526 MediaTracker 1053 Meldungsdialoge 560 Memory Mapping 888 Mengen (Set) Eigenschaften 770 java.util.EnumSet 770 java.util.HashSet 770 java.util.LinkedHashSet 770 java.util.TreeSet 770, 785, 786 Methoden 770, 771 Menüleisten 526 Menüs 545 Aufbau 546 Elemente aktivieren 548 Elemente deaktivieren 633 Ereignisbehandlung 550 erstellen 546 für Konsolenanwendungen 196 in Fenster einfügen 547 Kontextmenüs 549 Kontrollkästchen 548 Optionsfelder 548 Popup-Menüs 549 Tastaturkürzel 552 – Accelerator 553 – Mnemonics 552 Trennstriche 547 Untermenüs 548 Message Digest 1091 Metadaten 947, 1097 Metal (Look&Feel) 519 method() (HttpURLConnection) 1011 Methoden 67, 229 abstrakte 418 Argumente 236 Array-Parameter 282 Aufgaben 267 Aufruf durch Virtual Machine 389 aufrufen 231, 235 beenden 241 Datenaustausch zwischen Methoden 232 definieren 230
1207
Index
Ländercodes 910 Sprachcodes 910 Standardlokale 907 Lokale Klassen 322 Lokalisierung 895, 907 Lokale 907 nationale Formatierungen 914 – DateFormat 914 – Datum 914 – Gleitkommazahlen 914 – NumberFormat 914 – Prozentangaben 915 – Währungen 915 – Zeit 914 Ressourcenbündel 912 Ressourcendateien 912 Stringvergleiche 917 Unicode 907 Long 125, 145 Look&Feel 519 ändern 519 Ausführungsplattform 520 Ausnahmen 520 GTK 519 Mac 519 Metal 519 plattformunabhängige 520 UIManager 520 Windows 519 lookup() (Naming) 1023 lookupDefaultPrintService() (PrintServiceLookup) 655 lookupPrintServices() (PrintServiceLookup) 655 loop() (AudioClip) 704 lostOwnerShip() (ClipboardOwner) 557 Lovelace, Ada 111 L-Wert 154
Index
Factory-Methoden 268 finally-Block 448 generische 393 get/set-Methoden 262 lokale Klassen 322 lokale Variablen 232 main() 91 Parameter 235 – Argumente 236 – Basisklassenparameter 393 – definieren 235 – final 239 – Referenztypen 236 – Übergabeverhalten ändern 237 – Werttypen 236 parametrisierte 402 periodische Ausführung 753 polymorphe 384 rekursive 323 Rückgabewerte 240 – als Ergebnisse 240 – zur Fehlermeldung 434 Signatur 247 Stack 333 statische Methoden 245 this 242, 243 throws-Deklaration 443 trigonometrische 165, 331 Überladung 247, 376 Überschreibung 374, 385 – Bindung 386, 387 – Polymorphie 385 – verhindern 390 – Zugriffsspezifizierer 385 variable Anzahl Argumente 249 Verdeckung 390 zu bestimmtem Zeitpunkt ausführen 753 Microsoft 38 .NET 41 ActiveX 40 Antitrust Case 39 C# 41 Ungarische Notation 105 Visual J++ 41 Millionenquiz 711 min() (Collections) 788 min() (Math) 163 Mixer 707 mkdir() (File) 832 mkdirs() (File) 832 Mnemonics 44 Model (MVC-Architektur) 508 Modifizierer (MySQL) 924, 925 Modularisierung 58
1208
Modulo 157 Monitore 801, 806 Mosaic-Browser 37 MouseAdapter 500, 588 mouseClicked() (MouseListener) 500 mouseDragged() (MouseMotionListener) 500 mouseEntered() (MouseListener) 500 MouseEvent 499, 588 mouseExited() (MouseListener) 500 MouseListener 500, 550 MouseMotionAdapter 500 MouseMotionListener 500 mousemoved() (MouseMotionListener) 500 mousePressed() (MouseAdapter) 588 mousePressed() (MouseListener) 500, 552 mouseReleased() (MouseAdapter) 588 mouseReleased() (MouseListener) 500, 552 MouseWheelEvent 499 MouseWheelListener 500 mouseWheelMoved() (MouseWheelListener) 500 moveCaretPosition() (JTextComponent) 621, 622, 644 moveTo() (GeneralPath) 601 moveToInsertRow() (ResultSet) 942 MS-DOS-Eingabeaufforderung 48 MulticastSocket 989 Multiple Document Interface (MDI) 526, 676 Multithreading 791 MVC-Architektur 508, 618 Controller 618 für Swing-Komponenten 618 Model 618 View 618 MySQL 923 beenden 1167 Datenbanken – anlegen 928 – auflisten 929 – Daten
abfragen 931 eingeben 930 importieren 931 – öffnen 929 – Tabellen anlegen 929 Datentypen 925, 955 Installation 1165 JDBC-Treiber 1167 Modifizierer 924, 925 – AUTO_INCREMENT 924 – DEFAULT 924 – KEY 925 – NULL 924 – UNIQUE 925 – UNSIGNED 925 mysql.exe 928
Index
N ■■■■■■■■■■ name() (Aufzählungen) 295 Namensgebung, für Bezeichner 120 Namenskonflikte 299 Namespace (XML) 1111 Naming 1022 NaN 130, 436 nanoTime() (System) 752 native 1063 nativeSQL() (Connection) 960 NavigableMap 778 NavigableSet 770 NCSA 37 Nebeneffekte 179, 214 NetBeans 83 NetPC 36 Netscape 37 Netzwerke 967 Domain-Namen 972 Gateways 967 HTTP-Protokoll 1001, 1007 IP-Adresse 970 ISO-OSI-Referenzmodell 968 LAN (local area network) 967 Multicast-Sockets 989 Ports 972 Protokolle – IP 970 – TCP 970, 976 – UDP 970, 984, 989 Routing 969 Sockets 974, 993 TCP/IP-Stack 969 TCP-Sockets 976 UDP-Sockets 984 WAN (wide area network) 967 Netzwerkprogrammierung CORBA 1017 Datagram-Sockets 984, 998 Firewalls 989, 993 HTTP 1001 IP-Adressen abfragen 974 ipconfig 1026 Multicast-Sockets 989 Netzwerke 967
Portnummer 976 RMI 1017 Router 989, 993 Skeleton 1018 Socketverbindungen 967 – blockierend 974 – Datagram-Sockets 985 – Daten austauschen 977 – Multicast-Sockets 989 – nicht-blockierend 993 – Streams 977 – Timeout festlegen 977 – Verbindung aufbauen 977 – Verbindung schließen 978 Stream-Sockets 976, 994 Stub 1018 Timeout 977 Neue Zeile-Zeichen 136 Neuerungen, in Java 6 30 new 222 New Java I/O 877 Buffer Views 883 Dateien sperren 891 Datenkonvertierung 883 Datentransfer 886 Kanäle 877 Memory Mapping 888 Puffer 878 Socketkommunikation 993 newAudioClip() (Applet) 705 next() (Iterator) 781 next() (ResultSet) 938, 940 next() (Scanner) 101, 865 nextAfter() (Math) 164 nextDouble() (Random) 166 nextDouble() (Scanner) 101 nextFloat() (Random) 166 nextIndex() (ListIterator) 782 nextInt() (Random) 166 nextInt() (Scanner) 101 nextLine() (Scanner) 101, 865 nextLong() (Random) 166 nextToken() (StreamTokenizer) 855 nextToken() (StringTokenizer) 736 nextUp() (Math) 164 nextXxx() (Scanner) 865 NO_SUCH_PAGE (Printable) 650 Node 1118 normale 531 NoSuchElementException 864 NoSuchMethodException 442 Notepad-Editor 91 notify() (Thread) 809 notifyAll() (Thread) 812
1209
Index
mysqlimport.exe 931 Semikolon 930 Server 1165 starten 928, 1166 User anlegen 1167
Index
NotSerializableException 859 null (Referenz) 224 Null-Layout 493 NullPointerException 224 NumberFormat 914 NumberFormatException 142, 439 Numerische Promotion 151
O ■■■■■■■■■■ Oak 36 Object 171, 380, 399 clone() 173 equals() 173 finalize() 173 getClass() 173 hashCode() 174 Methoden 173 Threadsynchronisierung 173 toString() 174 ObjectInputStream 858 ObjectOutputStream 858 object-Tag 1047 Objekte 65, 217 als Basisklassenobjekte verwenden 383 als Felder 226 als Parameter 224, 236 Arrays 274 Arten 341 auf Konsole ausgeben 451 auflösen 264 deserialisieren 858 erzeugen 116, 222 finalize() 265 Hash-Code 172 Heap 313 im Arbeitsspeicher 71 Instanzbildung 70 kopieren 453 – flaches Kopieren 454 – tiefes Kopieren 458 Lebensdauer 311 null-Referenz 224 Objektvariablen 65, 222 Referenzen kopieren 223 serialisieren 857 – Externalizable 862 – Serializable 859 – SUID 862 – transient 858 – Vorgehensweise 860
1210
Speicherbereinigung 265, 313 vergleichen 460 – compareTo() 465 – equals() 461 – Gleichheit 461 – Größenvergleiche 465 Zugriff auf Elemente 71 Zugriff auf Felder 223 Zugriff auf Methoden 223 Objektorientierte Programmierung 62 generische 391 Information Hiding 260 Instanzbildung 70, 222 Kapselung 217 Klassen 62 – Definition 70 – Instanzbildung 70 – Konstruktoren 72 – Sicherheit 73 – statische Elemente 75 Konstruktoren 72 Objektorientiertes Denken 67 Polymorphie 381 Schnittstellen 419 Sicherheit 73 statische Klassenelemente 228, 245 Überladung 72 Überschreibung 385 Vererbung 76, 355 Vorteile 68 Objektorientiertes Denken 339 Objektorientiertes Design 341 Abhängigkeit 349 Aggregation 349 Analyse-Phase 345 Anforderungsspezifikation 345 Anwendungsfalldiagramme 346 Anwendungsfälle (use cases) 345 Assoziation 349 Design-Phase 348 Generalisierung 349 Implementierung 350 Klassendiagramme 348 Szenarien 346 UML 344 Objektvariablen 65 Objektzähler 228 Ocean (Metal-Look&Feel) 519 ODBC (Open Database Connectivity) 923 OEM-Zeichensatz 134, 875 offer() (Queue) 774 op= 161 open() (Selector) 996
Index
relationale (Vergleichsoperatoren) 162, 184 Strings 138, 168 Tabellenübersicht 177, 1174 ternäre 151 unäre 151 Vergleiche 154 Vorzeichen 155 Zuweisung 153, 161 Optimierung Daten kopieren 888 Schleifen 205 Optionsfelder 484, 526, 539 ordinal() (Aufzählungen) 296 out 853 OutputStream 840, 977 OutputStreamReader 847 OverlayLayout 513
P ■■■■■■■■■■ package 303 PAGE_EXISTS (Printable) 650 pageDialog() (PrinterJob) 654 PageFormat 650 Paint 598 paint() (Applet) 1043, 1045 paint() (Component) 478, 572, 585 paintAll() (Component) 478 paintBorder() (JComponent) 573 paintChildren() (JComponent) 573 paintComponent() (JComponent) 573, 574, 585 paintImmediately() (JComponent) 528, 587, 613 Pakete 102, 299 auf Festplatte 303 Elemente in Paketen definieren 303 erzeugen 303 Gültigkeitsbereich 299 kompilieren 304 Konstanten statisch importieren 302 Namen aus Paketen importieren 301 Namenskonflikte 299 Ressourcenbündel 901 statische Elemente importieren 301 Swing 509 unnamed 255, 303 Zugriff auf Elemente 301 Zugriffsbeschränkung 255 Panel 476, 485 Parameter 59, 235 Argumente 236 Arrays 282 definieren 235
1211
Index
open() (ServerSocketChannel) 996, 997 openConnection() (URL) 1002 openConnection() (URLConnection) 1005 openStream() (URL) 1002 Operatoren 48, 149, 272 -- 157 - (Subtraktion) 155 ! 190 != 154, 184 % 156 & 159 && 188 () (Methoden) 230 () (Typumwandlung) 155 * 155 + (Addition) 155 + (Konkatenation) 168 ++ 157 / 156 < 160 = 153, 184 == 154, 184 > 184 >= 184 >> 161 >>> 161 ? 194 ^ 159, 189 {} (Anweisungsblöcke) 152 {} (Array-Initialisierung) 273 | 159 | (logisches ODER) 189 || 189 ~ 160 arithmetische 155 Ausdrücke 152 Auswertungsreihenfolge 175 binäre 151 Bit-Operatoren 158 Cast-Operator 155 Dekrement 157 Division 156 Inkrement 157 instanceof 397 Klammern 175, 334 Klassifizierung 150 L- und R-Wert 154 logische 168, 188 Modulo 157 Nebeneffekte 179, 214 numerische Promotion 151 op= 161 Operandenauswertung 176 Priorität 175
Index
Felder 306 final 239 für Annotationen 1100 für Applets 1054 Gültigkeitsbereich 306 Referenztypen 236 RMI 1026 Übergabeverhalten ändern 237 variable Anzahl Argumente 249 von Basisklassentypen 393 Werttypen 236 paramString() (AbstractButton) 531 paramString() (AWTEvent) 502 parseDouble() (Double) 98, 435 parseFloat() (Float) 98 parseInt() (Integer) 98, 142 parseLong() (Long) 98 Parsen 855 Pascal 64 Passwörter GUI-Anwendungen 527 Konsolenanwendungen 876 paste() (JTextComponent) 620 PATH, Umgebungsvariable setzen 86 Pattern 738, 741 PatternSyntaxException 739 peek() (Queue) 775 peek() (Stack) 766 Peers 486 Periodische wiederkehrende Ausführung 753 PI (Math) 162 PipedInputStream 813 PipedOutputStream 813 PipedReader 813 PipedWriter 813 Pipe-Kommunikation 813 Pipes 813 PlainDocument 619, 628, 634 Plattformunabhängigkeit 80 play() (AudioClip) 704 Plug-in, Java 81, 962, 1042, 1048 Point 589 Policy-Datei 1032, 1087 policytool 1087 poll() (Queue) 775 Polygon 600 Polymorphie 381 abstrakte Basisklassen 415 Basisklassenarrays 391 Basisklassenparameter 393 Bindung 387 – dynamische 388 – statische 388
1212
echte 386 Object 399 polymorphe Methoden 384 polymorphe Variablen 381 Reduzierung auf Basisklassentypen 383 Rückverwandlung in abgeleitete Typen 383 Schnittstellen 424 Typidentifizierung 396 – durch getClass() 398 – durch instanceof 397 – durch überschriebene Methoden 396 Typumwandlung 384 Überschreibung 385 pop() (Stack) 766 Popup-Menüs 526, 549 Port 707 Portierbarkeit 79 Ports 972 position() (ByteBuffer) 882 position() (Clob) 958 position() (FileChannel) 881 position() (FileLock) 892 Positionsanzeige (Caret) 621 Postfix-Notation 766 pow() (Math) 163 präemptives Scheduling 817 PreparedStatement 952 preparedStatement() (Connection) 951 previous() (ListIterator) 782 previous() (ResultSet) 940 previousIndex() (ListIterator) 782 print() (Component) 478 print() (JComponent) 528 print() (JTable) 687 print() (JTextComponent) 649, 660 print() (Printable) 648, 650 print() (PrintStream) 95, 141, 846 print() (PrintWriter) 846 Printable 648, 650 printAll() (Component) 478 printDialog() (PrinterJob) 654 PrinterJob 647, 652 printf() (Console) 96, 875 printf() (System.out) 95, 869 println() (PrintStream) 95, 141, 846 println() (PrintWriter) 846 PrintRequestAttributeSet 658 PrintService 655 PrintServiceLookup 648, 655 printStackTrace() (Throwable) 441 PrintStream 846 PrintWriter 846, 980 Priorität Operatoren 175
Index
Protokolle HTTP 1001, 1007 IP (Internet Protocol) 970 JRMP 1018 RMI 1018 TCP (Transmission Control Protocol) 970, 976 UDP (Universal Datagram Protocol) 970, 984, 989 Proxy 1013 Prozentzeichen 227, 871 Prozesse 791 Prozessor 44, 791 public 254 public (für Klassen) 91, 256 public (für Klassenelemente) 220, 257 public (für main()) 91 public (Schnittstellen) 422 publish() (SwingWorker) 823 Puffer (NIO) 878 push() (Stack) 766 pushBack() (StreamTokenizer) 856 PushbackInputStream 849 PushbackReader 849 put() (ByteBuffer) 882 put() (Map) 777
Q ■■■■■■■■■■ quadTo() (GeneralPath) 601 Quellpfad 1140 Quelltextdateien, Benennung 1143 Queue 758 Quickinfo 527, 528, 555 Quicksort 280
R ■■■■■■■■■■ Radiant 165 Random 166 random() (Math) 163, 165 RandomAccessFile 843 RDBMS 922 read() (DatagramChannel) 999 read() (FileChannel) 881 read() (ImageIO) 604 read() (InputStream) 839 read() (JTextComponent) 620, 636 read() (RandomAccessFile) 844 read() (SocketChannel) 998 Reader 838 reader() (Console) 875
1213
Index
Reguläre Ausdrücke 738 PriorityQueue 774 private 254 private (für Klassen) 256 private (für Klassenelemente) 220, 256 private (für Methoden) 390 Process 751 process() (SwingWorker) 823 Programme Ablauf 333 Ablauf und Exception-Behandlung 447 Applets 1041 aus mehreren Quelldateien 351 – ausführen 304, 353 – kompilieren 304, 353 beenden 435, 474, 510, 705, 750 erstellen 82 Fehlersuche 1161 GUI-Anwendungen 471 Konsolenanwendungen 48, 89 Programmerstellung 79 Ablauf 82 Bytecode 82 Divide-and-Conquer-Technik 67 Konsolenanwendungen 91 objektorientierte Problemlösung 339, 341 Programm ausführen 93 Quelltext eingeben 91 Quelltext kompilieren 92 Software-Enwicklungszyklus 342 Programmiergrundlagen 43 Programmiergrundlagen siehe Allgemeine Programmiergrundlagen Programmiersprachen Algol 52 Assembler 44 Basic 47 C 52 C# 41 Cobol 52 Fortran 52 höhere 46 imperative 52 objektorientierte 62 Paradigmen 52 Pascal 64 strukturierte 61 typisierte 52 Prompt 48 Properties 748 propertyNames() (Properties) 748 protected 254, 363 protected (für Klassen) 256 protected (für Klassenelemente) 257
Index
readExternal() (Externalizable) 862 readLine() (BufferedReader) 100, 439, 842 readLine() (Console) 97, 875, 877 readLine() (RandomAccessFile) 844 readObject() (ObjectInputStream) 859, 861 readPassword() (Console) 875 readUTF() (DataInputStream) 849 readUTF() (ObjectInputStream) 859 readUTF() (RandomAccessFile) 844 readXxx() (DataInputStream) 849 readXxx() (ObjectInputStream) 859 readXxx() (RandomAccessFile) 844 rebind() (Naming) 1022 receive() (DatagramChannel) 1000 receive() (DatagramSocket) 984, 985 receive() (Multicast) 990 Rectangle 600 Redeklaration 307 Referenzen 116 null 224 Referenztypen 115 vergleichen 154 zuweisen 153 Referenztypen 70, 116 Arrays 271 Aufzählungen (enum) 291 Klassen 217 Objekterzeugung 116 Schnittstellen 419 Vergleiche 117 versus Werttypen 314 Zuweisung 117 Register 44 register() (ServerSocketChannel) 996 Registerbereiche 526 Reguläre Ausdrücke 737 Rehashing 777 Rekursion 323 Relationale Datenbanken 921 release() (FileLock) 892 remaining() (ByteBuffer) 882 Remote 1021 RemoteException 1021, 1039 remove() (ButtonGroup) 534 remove() (Collection) 762 remove() (DefaultMutableTreeNode) 679 remove() (Iterator) 781 remove() (List) 765 remove() (ListIterator) 782 remove() (Map) 777 remove() (Queue) 775 remove() (Set) 771 remove() (SystemTray) 694 removeAttribute() (Element) 1123
1214
removeChild () (Node) 1124 removeItem() (JComboBox) 541, 543 removeTab() (JTabbedPane) 674 removeUpdate() (DocumentListener) 632 removeXXXListener() (Component) 478 renameTo() (File) 832 RenderedImage 604 repaint() (Component) 478, 587, 613 replace() (String) 170 replace() (StringBuffer) 735 replace() (StringBuilder) 735 replaceAll() (Collections) 788 replaceChild() (Node) 1124 replaceSelection() (JTextComponent) 622 reset() (ByteBuffer) 882 reset() (InputStream) 839 reshape() (JComponent) 528 ResourceBundle 896 Ressourcen 895 Bilder 901 einlesen 899 Ressourcenbündel 896, 912 – auswählen 913 – laden 899 – Pakete 901 Ressourcendateien 898, 912 – anlegen 898 – aufsetzen 912 – auswählen 913 – Format 898 – in XML 902 – laden 899 Strings 896 Vorteile 895 ResultSet 937 ResultSetMetaData 949 retain() (Collection) 762 return 240 Ergebnisse aus Methoden zurückliefern 240 Methoden verlassen 241 reverse() (Collections) 788 rewind() (ByteBuffer) 881, 882 RGB-Farben 579 Rigid Areas 514 rint() (Math) 163 RMI 1017 CLASSPATH 1031 Client – implementieren 1023 – Remote-Objekte referenzieren 1023 – starten 1025 Grundarchitektur 1017 JMPR 1017 lokale Anwendung erstellen 1020
Index
S ■■■■■■■■■■ SAX (XML) 1112 SAXParserFactory 1113 scalb() (Math) 164 scale() (Graphics2D) 603 Scanner 100, 863 Schaltflächen 483, 531, 536, 539 gruppieren 534 Wechselschalter 532 schedule() (Timer) 753 scheduleAtFixedRate() (Timer) 754 Scheduling 791, 815 kooperatives 816 präemptives 817 Schema (XML) 1107, 1110 Schichtungsbereiche 526 Schieberegler 526 Schleifen 51, 198 Abbruchbefehle 212 – break 208 – continue 208 – mit Sprungmarken 211 Arrays durchlaufen 275 Ausführung 199 do-while 202 Endlosschleifen 204, 209 for 203 for-each 204 gewollte Endlosschleife 209 Iteration 200 leere Schleifen 213 mit mehreren Schleifenvariablen 205 Optimierung 205 reduzierte for-Schleife 213 Schleifenvariable 51, 199 Warteschleifen 214 while 198 Schlüssel (Codierung) 1091 Schlüsselwörter 54, 120, 1173 Schnittstellen 419 definieren 423 implementieren 424 – durch anonyme Klassen 430 – einzelne 424 – mehrere 426 nutzen 424 Polymorphie 424 SwingConstants 529 vererben 423 versus Klassen 422 versus Vererbung 421 Vorteile 421
1215
Index
Parameterübergabe 1026 Probleme 1025 Remote-Schnittstelle – definieren 1021 – implementieren 1021 rmic 1024 rmiregistry 1019 Security-Manager 1032 Server – implementieren 1022 – Remote-Objekte anmelden 1022 – Remote-Objekte erzeugen 1022 – starten 1025 Skeleton 1018 – generieren 1024 Stub 1018 – generieren 1024 Systemregistrierung 1019 – starten 1025, 1039 vs. CORBA 1017 RMI-Anwendungen Codebase 1033 Parameter-Klassen laden 1031 Security-Manager 1032 Stub laden 1031 rmic 1024 rmiregistry 1019 RMISecurityManager 1032 Robustheit 81 rollback() (Connection) 950 RootPane 510 rotate() (Graphics2D) 602 Rot-Schwarz-Bäume 771 round() (Math) 163 Router 989, 993 Routing 969 rowAtPoint() (JTable) 687 RowFilter 689 RTTI 396 RTTI siehe Typidentifizierung Rückgabewerte 59, 240 als Ergebnisse 240 zur Fehlermeldung 434 run() (Runnable) 799 run() (Thread) 793 run() (TimerTask) 753 Rundungsfehler 132, 330 Runnable 799 Runtime 750 RuntimeException 442 Runtime-Packages 1085 R-Wert 154
Index
Schriften 575, 637 Abmaße 583 Font-Objekte – anpassen 638 – erzeugen 641 installierte Schriftarten ermitteln 641 Scrollable 623 Scrollbar 476 Scrolling 623, 629 ScrollPane 476 ScrollPanelLayout 513 search() (Stack) 766 Security Policy 1087 SecurityException 1032 Security-Manager 1032 SecurityManager 1086 seek() (RandomAccessFile) 843 Seiteneffekte 179 select() (Selector) 994, 997 selectAll() (JTextComponent) 622 selectedKeys() (Selector) 994, 997 SelectionKey 994 Selector 993, 996 Semikolon Java 202, 213 MySQL 930 send() (DatagramChannel) 1000 send() (DatagramSocket) 985, 986 send() (Multicast) 990 Serialisierung 857 API-Klassen 859 Externalizable 862 Konzept 857 Serializable 859 Streamklassen 858 SUID 862 transient 858 Virtual Machine 860 Vorgehensweise 860 Serializable 859, 1026 Server Proxy 1013 Zugriff über Passwort 1014 ServerSocket 977, 983 ServerSocketChannel 994 Sessions 1004 Set 758, 771 set() (Calendar) 744 set() (ListIterator) 782 setAccelerator() (JMenuItem) 547, 553 setAlignment() (Label) 482 setAsciiStream() (Clob) 958 setAsciiStream() (PreparedStatement) 958 setAttribute() (Element) 1123
1216
setAutoCommit() (Connection) 950 setBackground() (Component) 478, 580, 1045 setBinaryStream() (Blob) 957 setBinaryStream() (PreparedStatement) 958 setBorder() (JComponent) 528 setBorder() (JPanel) 612 setBounds() (Component) 478, 493, 1044 setBytes() (Blob) 957 setCaret() (JTextComponent) 621 setCaretColor() (JTextComponent) 621 setCaretPosition() (JTextComponent) 621, 622 setCellSelectionEnabled() (JTable) 686 setCharacterStream() (Clob) 958 setClip() (Graphics) 614 setClosable() (JInternalFrame) 677 setColor() (Graphics) 576, 578 setColumnSelectionAllowed() (JTable) 686 setContentPane() (JFrame) 512 setContents() (Clipboard) 556 setContinousLayout() (JSplitPane) 675 setCopies() (PrinterJob) 652 setCursor() (Component) 478 setDaemon() (Thread) 802 setDaemon() (ThreadGroup) 819 setData() (DatagramPacket) 985 setDefault() (Locale) 908 setDefaultCloseOperation() (JFrame) 510, 511 setDividerLocation() (JSplitPane) 675 setDocument() (JTextComponent) 620 setDoInput() (URLConnection) 1005 setDoOutput() (URLConnection) 1005 setDoubleBuffered() (JComponent) 528, 591 setDragEnabled() (Drag-Komponenten) 698 setDropMode() (Drop-Komponenten) 698 setEditable() (JTextComponent) 620, 624 setEnabled() (AbstractButton) 531 setEnabled() (Component) 478 setEnabled() (JMenuItem) 547 setEnabledAt() (JTabbedPane) 674 setErr() (System) 752, 853 setFileFilter() (JFileChooser) 562 setFont() (Component) 478 setFont() (Graphics) 575 setForeground() (Component) 478 setHorizontalAlignment() (AbstractButton) 531 setHorizontalAlignment() (JLabel) 529 setHorizontalGroup() (GroupLayout) 515, 516 setHorizontalTextPosition() (AbstractButton) 531 setHorizontalTextPosition() (JLabel) 529 setIcon() (AbstractButton) 531 setIcon() (JLabel) 529 setIconifiable() (JInternalFrame) 677 setImageAutoSize() (TrayIcon) 694 setIn() (System) 752, 853
Index
setState() (Checkbox) 484 setStrength() (Collator) 918 setString() (Clob) 958 setString() (JProgressBar) 544 setStroke() (Graphics2D) 597 setTabComponent() (JTabbedPane) 674 setTabComponentAt() (JTabbedPane) 674 setText() (AbstractButton) 531 setText() (JLabel) 529 setText() (JTextComponent) 620 setText() (Label) 482 setTime() (Calendar) 744 setTimeToLive() (Multicast) 990 setTitle() (Frame) 474 setToolTipText() (JComponent) 528 setTransform() (Graphics2D) 603 setValue() (JProgressBar) 544 setValueAt() (AbstractTableModel) 684 setValueAt() (JTable) 687 setVerticalAlignment() (AbstractButton) 531 setVerticalTextPosition() (AbstractButton) 531 setVisible() (Component) 478 setWrapStyleWord() (JTextArea) 628 setXORMode() (Graphics) 581 Sherman Act 39 Short 145 short 125 show() (JPopupMenu) 549 showConfirmDialog() (JOptionPane) 560 showDialog() (JColorChooser) 564 showDialog() (JFileChooser) 562 showInputDialog() (JOptionPane) 560 showMessageDialog() (JOptionPane) 560 showOpenDialog() (JFileChooser) 562 showSaveDialog() (JFileChooser) 562 shuffle() (Collections) 788 Shutdown-Hooks 750 shutdownOutput() (Socket) 980, 984 Sicherheit 1083 Applets 1061, 1086 Aufgaben des Entwicklers 1092 Aufgaben des Kunden 1094 digitale Unterschrift 1090 Message Digest 1091 policies 1087 Policy-Datei 1032, 1087 policytool 1087 Sandbox-Prinzip 1062 Schlüssel 1091, 1093 Security Policy 1087 Sicherheitsmanager 1086 Signieren 1093 Signierter Code 1090 Spyware 1083
1217
Index
setJobName() (PrinterJob) 652 setLabel() (Button) 483 setLabel() (Checkbox) 484 setLayout() (Container) 493 setLayout() (JFrame) 512 setLayout() (Panel) 486 setLength() (RandomAccessFile) 843 setLineWrap() (JTextArea) 628 setListData() (JList) 540 setLocation() (Component) 478 setLocation() (JInternalFrame) 677 setLookAndFeel() (UIManager) 519, 520 setMaximizable() (JInternalFrame) 677 setMaximum() (JProgressBar) 544 setMaximumSize() (JComponent) 512, 528 setMaxPriority() (ThreadGroup) 819 setMinimum() (JProgressBar) 544 setMinimumSize() (JComponent) 512, 528 setMnemonic() (JMenuItem) 547 setModalityType() (Dialog) 570 setName() (Thread) 803 setOneTouchExpandable() (JSplitPane) 675 setOut() (System) 752, 853 setPaint() (Graphics2D) 598 setPaintMode() (Graphics) 581 setPreferredSize() (JComponent) 512, 528 setPrintable() (PrinterJob) 652 setPriority() (Thread) 803, 816 setProperty() (Properties) 748 setRequestProperty() (HttpURLConnection) 1011 setRequestProperty() (HttpUrlConnection) 1015 setRows() (JTextArea) 628 setRowSelectionAllowed() (JTable) 686 setRowSorter() (JTable) 687 setSecurityManager() (System) 752, 1086 setSelected() (AbstractButton) 531 setSelectedCheckbox() (CheckboxGroup) 485 setSelectedFile() (JFileChooser) 562 setSelectedIndex() (JList) 540, 542 setSelectedIndices() (JList) 542 setSelectedTextColor() (JTextComponent) 622 setSelectionEnd() (JTextComponent) 622 setSelectionMode() (DefaultTreeSelectionModel) 681 setSelectionMode() (Jlist) 542 setSelectionMode() (JTable) 686 setSelectionModel() (JTree) 681 setSelectionPath() (JTree) 681 setSelectionStart() (JTextComponent) 622 setSize() (Component) 478 setSoTimeout() (DatagramSocket) 985 setSoTimeOut() (ServerSocket) 983 setSoTimeout() (ServerSocket) 977 setSoTimeOut() (Socket) 983 setSource() (AWTEvent) 502
Index
von Klassen 73, 261 Zertifikate 1092 Zugriffsrechte 1088 Sicherheitsmanager 1086 Signatur 247 Signieren 1093 Signierter Code 1090 Signifikante Stellen (von Gleitkommatypen) 131 signum() (Math) 163, 164 SimpleDateFormat 743, 745 SimpleDoc 657 sin() (Math) 164 Single-Thread-Rule 821 sinh() (Math) 164 size() (ByteArrayOutputStream) 851 size() (Collection) 762 size() (FileLock) 892 size() (List) 765 size() (Map) 778 size() (Set) 771 Skeleton 1018 skip() (Scanner) 865 skipBytes() (RandomAccessFile) 843 sleep() (Thread) 215, 800 Smalltalk 81 SOA 1129 Socket 983 socket() (DatagramChannel) 999 socket() (ServerSocketChannel) 997 SocketChannel 878, 994 Sockets 974, 993 blockierende Ein-/Ausgabe 974 Datagram 984, 998 Multicast 989 Non-blocking I/O 993 Selector 993 Stream 976, 994 TCP 976 UDP 984 SocketTimeoutException 983 Socketverbindungen 967 blockierend 974 Datagram-Sockets 985 Daten austauschen 977 Multicast-Sockets 989 nichtblockierend 993 Streams 977 Timeout 977 Timeout festlegen 977 Verbindung aufbauen 977 Verbindung schließen 978 Software-Entwicklung Divide-and-Conquer 67
1218
Enwicklungszyklus 342 – Analyse 342 – Anforderungsspezifikation 342 – Design 342 – Implementierung 342 – Test 342 Objektorientierte Problemlösung 67 Solaris 36 Sonderzeichen 134, 135, 136 Zeilenumbruch 136 sort() (Arrays) 280, 467 sort() (Collections) 788 Sortieren Arrays 280 Arrays von Objekten 467 Collections 467 in Containern (Collections) 783 Quicksort 280 Strings 731 Sound 704 in Applets 1053 Klangdateien 704 MIDI 706 mit AudioClip 704 mit der Sound API 706 Sampled Audio 706 Sampling 706 Streaming 708 SourceDataLine 707, 708, 710 Speicherbelegung 133 Arrays 274 Gleitkommatypen 129 Integer-Typen 126 mehrdimensionale Arrays 286 Zeichentyp (char) 133 Speicherbereiche 332 Codespeicher 332 Heap 332 Stack 332 Speicherbereinigung 265 Algorithmus 265 finalize() 265 Objekte 313 System.gc() 265 von Programm aus starten 752 SplashScreen 691 SpringLayout 513 Sprunganweisungen 206 für Schleifen 208 für Verzweigungen 208 mit Sprungmarken 211 Sprünge 49 Sprungmarken 211
Index
store() (Properties) 748 Streams 837 abstrakte Basisklassen 837 Dateistreams 840 Daten parsen 855 für Streaming Sound 708 Klassenhierarchie 837 Positionszeiger 839 Reader/Writer-Klassen 838 Serialisierung 858 Socketverbindungen 977, 980 Speicherstreams 850 Standardein- und -ausgabe 853 Stream-Sockets 976 nichtblockierende I/O 994 StreamTokenizer 855 strictfp (für Klassen) 219, 332 strictfp (für Methoden) 220, 332 String 102, 137, 169 StringBuffer 733 StringBuilder 733 StringReader 852 Strings 45, 137, 727 +-Operator 138, 168 Abmaße 583 als Ressourcen 896 direkte Manipulation 733 Escape-Sequenzen 135 in Speicher einlesen 852 intern() 140 JNI 1071 Konstruktoren 169 kopieren 138 leere 185 Literale 137 Methoden 170 null 185 Pooling 140 reguläre Ausdrücke 737 sortieren 731 StringBuffer 733 StringBuilder 733 Tabulatoren 136 Typumwandlungen 140 Unicode 133, 729, 875 Unicode-Zeichen einbauen 134 vergleichen 185, 737, 917 verketten 168 Wagenrücklauf-Zeichen 136 Zeichensatzkodierung 728 Zeilenumbrüche 137 Zeilenumbruchzeichen 136 Zuweisung 138
1219
Index
Spyware 1083 SQL (Structured Query Language) 923 Aggregatfunktionen 927 – COUNT() 927 – MAX() 927 – MIN() 927 – SUM() 927 CREATE TABLE 924 DELETE 926 INSERT 926 SELECT 926 UPDATE 926 SQLException 935, 960 SQLWarning 961 sqrt() (Math) 164 Stack 333, 766, 773 Auflösung durch Exceptions 447 Stackrahmen 333 Standardausgabe 853 Standardbibliothek 80, 102 Standardbibliothek siehe API Standarddialoge 526, 559 Standardeingabe 853 Standardwerte 225 Standardzeichenkodierung 846 abfragen 846 ändern 847 Star7 37 start() (Applet) 1043, 1045 start() (Matcher) 739, 741 start() (Thread) 793, 798 Startbildschirme 690 anzeigen 691 JAR-Archive 691 Programmierung 691 startDocument() (DefaultHandler) 1113 startElement () (DefaultHandler) 1113 startsWith() (String) 170 stateChanged() (ChangeListener) 568 Statement 935 static (für Anweisungsblöcke) 269 static (für Felder) 228 static (für innere Klassen) 219, 322 static (für Methoden) 245, 390 static import 301 Steuerelemente 473, 525 AWT 476 einrichten 478 in Container einfügen 479 Swing 525 Stilkonventionen 105 stop() (Applet) 1043 stop() (AudioClip) 704
Index
StringSelection 556 StringTokenizer 736, 857 stringWidth() (FontMetrics) 583 StringWriter 852 Strukturansichten 527 Struts 514 Stub 1018 StyledDocument 619 substring() (String) 171 substring() (StringBuffer) 735 substring() (StringBuilder) 735 Suchen binäre Suche 281 in Arrays 281 in Containern (Collections) 783 in Textkomponenten 643 lineare Suche 281 SUID 862 Sun Microsystems 35 Green-Projekt 36 Internet-PC 36 Solaris-Betriebssystem 36 super 369 supportsResultSetConcurrency() (DatabaseMetaData) 948 supportsResultSetType() (DatabaseMetaData) 948 swap() (Collections) 788 Swing 821 Applets 1050 ContentPane austauschen 512, 677 Double Buffering 590 Fenster 509 Grafik 583 Grundgerüst 508 JPanel 574 Klassennamen 509 Komponenten 525 – Basisklasse 527 – einfügen 511 – Übersicht 525 Layout-Manager 512, 513 Look&Feel 519 – ändern 519 – GTK 519 – Mac 519 – Metal 519 – UIManager 520 – Windows 519 MVC-Architektur 508 paintComponent() 573, 574, 585 Paket javax.swing 509 Panes 510 – ContentPane 510 – GlassPane 510
1220
– LayeredPane 510 – RootPane 510 Quickinfo 528 Single-Thread-Rule 821 Steuerelemente 525 SwingWorker 822 Textkomponenten 527 threadsichere Methoden 821 Timer 825 UI-Delegates 618 versus AWT 507 swing.properties 519 SwingConstants 529 SwingUtilities 822 switch 194 switch-Verzweigung 194 break 195 case-Marken 194 default-Marke 195 lokale Variablen 318 Symbolleisten 527, 554 Aufbau 554 ein- und ausblenden 551 Quickinfo 555 Standardsymbole 555 Synchronisierung (Threads) 804 Benachrichtigungen 809 Deadlock 808 gesicherter Abschnitte 806 Monitore 806 notify() 809 Pipes 813 synchronized 806 volatile 806 wait() 809 synchronized 806 synchronizedList() (Collections) 828 synchronizedMap() (Collections) 828 synchronizedSet() (Collections) 828 System 752, 853 System.err 432, 853 System.in 97, 853 System.out 95, 853 SystemColor 579 Systemprogrammierung 748 externe Programme ausführen 751 INI-Dateien 748 Shutdown-Hooks 750 Speicherbereinigung starten 752 Standardausgabe umlenken 752, 853 Standardeingabe umlenken 752, 853 Standardfehlerausgabe umlenken 752, 853 Umgebungsvariablen abfragen 752
Index
T ■■■■■■■■■■ Tabellen 527 Tabellendarstellungen (JTable) 682 TableColumnModel 683 TableModel 683 TableModelListener 687, 688 TableRowSorter 689 Tabulatorzeichen 136 Tags 1103 tan() (Math) 164 tanh() (Math) 164 TargetDataLine 707 Tastaturkürzel 552 TCP (Transmission Control Protocol) 970 TCP/IP-Stack 969 TCP-Sockets 976 Teilungsbereiche 526 Templates siehe Generics Text siehe Strings TextArea 476 TextComponent 476 TextEvent 499 TextField 476 Textkomponenten 527 TextListener 500 TexturePaint 598 textValueChanged() (TextListener) 500 Textverarbeitung 617 Aktionen 622 auf Textänderungen reagieren 632 Bildlaufleisten 623, 629 Caret setzen 621 Dateien öffnen-Dialog 561 Dateifilter 562 Drucken 647, 649, 660, 667 Fokus 644 Fonts 575, 637 geheime Daten einlesen 624 Grundlagen 617 Komponentenhierarchie 617 Markierungen 621 MVC-Architektur 618 nicht gespeicherte Änderungen 631 Positionsanzeige (Caret) 621
Schreibschutz 624 Schriften 575, 637 – Abmaße 583 – Font-Objekte
erzeugen 641 kopieren 638 – installierte Schriftarten ermitteln 641 Scrolling 623, 629 Tastaturkürzel 622 Text – markieren 622 – zeichnen 575 Textauswahl 621 Texteditoren 628 – Datei
öffnen 630, 635 speichern 630, 636 – Menübefehle deaktivieren 633 – Suchen 643 Textkomponenten – einzeilig 617, 624 – JEditorPane 619 – JPasswordField 624 – JTextArea 627 – JTextField 624 – JTextPane 619 – mehrzeilig 617, 627 – mit Formatierung 617 Zwischenablage 642 this 242 statische Methoden 245 Zugriff auf verdeckte Felder 244 Thread 792, 793 beenden 794 benachrichtigen 809 Collections 828 Dämonen 802 Deadlocks 808 Gruppen 818 java.io.PipedInputStream 813 java.io.PipedOutputStream 813 java.io.PipedReader 813 java.io.PipedWriter 813 java.lang.Runnable 799 java.lang.Thread 793 Kommunikation 813 Methoden 803 Monitore 801 Pipes 813 run() 794 Scheduling 815 – kooperatives 816 – präemptives 817
1221
Index
verfügbaren Speicher abfragen 750 Virtual Machine beenden 752 Zeitgeber 753 Zeitmessung 752 SystemTray 693
Index
schlafen legen 800 – sleep() 800 – yield() 801 schlafend 801 Single-Thread-Rule 821 starten 794, 798 Swing 821 SwingWorker 822 synchronisieren 804 – Benachrichtigungen 809 – Deadlock 808 – gesicherte Abschnitte 806 – Monitore 806 – notify() 809 – Pipes 813 – synchronized 806 – volatile 806 – wait() 809 Timer 825 unterbrechen 801 verwalten 815 – green Threads 815 – native Threads 815 warten 809 Zeitscheibenverfahren 817 Zustände 793 ThreadGroup 818 Thread-Programmierung 791 Applets 1051 Monitore 801 Multithreading 791 Prozesse 791 Prozessor 791 Scheduling 791 Shutdown-Hooks 750 Synchronisierung 804, 806 Threads 792 Throwable 441 throws 443 Timer 753, 825 TimerTask 753 toArray() (Collection) 762 toArray() (List) 765 toArray() (Set) 771 toByteArray() (ByteArrayOutputStream) 851, 852 toDegrees() (Math) 164, 165 Tokens 736, 855 Toolkit 557 ToolTips 527 toRadians() (Math) 164, 165 toString() (Arrays) 278 toString() (Aufzählungen) 296
1222
toString() (ByteArrayOutputStream) 851 toString() (Date) 743 toString() (Object) 141, 172, 174 überschreiben 451 toString() (StreamTokenizer) 856 toString() (StringBuffer) 735 toString() (StringBuilder) 735 toString() (StringWriter) 853 toString() (Throwable) 441 totalMemory() (Runtime) 750 toUpperCase() (String) 139 Transaktionen 949 Transferable 556 TransferHandler 699, 700, 701 transformFrom() (FileChannel) 887 transformTo() (FileChannel) 887 transient 858 translate() (Graphics) 581, 651 translate() (Graphics2D) 602 Transparenz 580 TrayIcon 693 TrayIcon() (TrayIcon) 694 TreeMap 778, 786 TreeModel 678 TreeNode 678 TreePath 682 TreeSet 770, 785, 786 Treiber (Datenbanken) 932 Trigonometrische Funktionen 331 trigonometrische Funktionen 165 trim() (String) 171 try 438 try-Blöcke 438 tryLock() (FileChannel) 892 Typidentifizierung 396 durch getClass() 398 durch instanceof 397 durch überschriebene Methoden 396 Typisierung 52 Typumwandlung 57, 142 Autoboxing 147 Casten 144 Cast-Operator 155, 384 explizite (narrowing) 144 implizite (widening) 143 Java Generics 414 Polymorphie 384 Referenztypen – DownCast 384 – UpCast 384 Strings 140
Index
Über- und Unterlauf 127, 328 Überladung 72, 247, 376 Überschreibung 374, 385 Bindung 387 Polymorphie 385 Zugriffsspezifizierer 385 UCS 728 UDP (Universal Datagram Protocol) 970 UDP-Sockets 984 UIManager 520 ulp() (Math) 164 Umgebungsvariablen CLASSPATH – für JDK 1140 – für MySQL 932 von Programm aus abfragen 752 UML 259, 344 Umlaute 94, 120, 875 uncaughtException() (ThreadGroup) 819 unchecked 1146 Ungarische Notation 105 UnicastRemoteObject 1040 Unicode 133, 134, 728, 907, 1176 UTF-16-Codierung 729 UTF-32-Codierung 729 UTF-8-Codierung 729 UnknownHostException 975 UnknownServiceException 1006 unnamed Paket 303 unread() (PushbackInputStream) 850 UnsupportedEncodingException 730 UnsupportedFlavorException 558 UnsupportedLookAndFeelException 520 UnsupportedOperationException 782 UpCast 384 update() (Component) 478, 591 update() (SplashScreen) 691 updateComponentTreeUI() (SwingUtilities) 520 updateRow() (ResultSet) 941 updateString() (ResultSet) 941 URIs (Uniform Resource Identifier) 1001 URL Uniform Resource Locator 1001, 1002 – Codierung 1004 – Sonderzeichen 1004 URLConnection 1004 URLDecoder 1004 URLEncoder 1004 useDelimiter() (Scanner) 865 useLocale() (Scanner) 865
useRadix() (Scanner) 865 UTC 742 UTF-16 729
V ■■■■■■■■■■ valueChanged() (ListSelectionListener) 541 valueOf() (String) 142, 171 values() (Aufzählungen) 294 Variablen 45, 118 Anfangswerte 121 Arrays 271 definitive assignment 122, 315 deklarieren 119 enum 292 Gültigkeitsbereiche 122 initialisieren 121 Instanzvariablen 229 Klassenvariablen 229 Lebensdauer 310 lokale 232, 305 Namensgebung 120 Objektvariablen 65, 222 polymorphe 381 Terminologie 229 Übersicht 314 Umlaute in Namen 120 Vector 765 Verdeckung 308 geerbter Felder 372 Methoden 390 this 244 Vererbung 76, 355 abgeleitete Klassen 355 Basisklassen 355 – abstrakte 415 – Unterobjekte 359 Einsatzgebiete 377 geerbte Elemente – Initialisierung 366 – überladen 376 – überschreiben 374, 385 – Verdeckung 372 – Zugriff 358 Grundprinzip 355 Klassenhierarchien 379 Konstruktoren 366 Mehrfachvererbung 358, 420 Methoden überladen 376 Methoden überschreiben 374, 385
1223
Index
U ■■■■■■■■■■
Index
Object 171, 380 Polymorphie – abstrakte Basisklassen 415 – Bindung 387 – überschriebene Methoden 385 Schnittstellen 423 super 369 und Klassen-Design 377 Verdeckung 372 verhindern 358 versus Komposition 379 versus Schnittstellen 421 Zugriffsspezifizierer 361 Vergleiche 50, 460 compareTo() 465 equals() 461 Gleichheit 461 Größenvergleiche 465 lexikografische 187 Operatoren 184 Strings 185, 917 Zeit- und Datumsangaben 743 Verschachtelung 'dangling else' 193 Gültigkeitsbereiche 308 if-Anweisung 193 Verzeichnisse 829, 832 Dateien auflisten 832 Dateifilter 834 Plattformunabhängigkeit 829 Trennzeichen 829 Verzweigungen 50, 190 'dangling else' 193 Abbruchbefehle 208 if-Anweisung 193 if-else-Verzweigung 191 switch-Verzweigung 194 vi 91 View (MVC-Architektur) 508 ViewportLayout 513 Virtual Machine 41, 82 ClassFormatError 1042 Classloader 1031, 1083 Konsole 874 Methodenaufrufe 389 Serialisierung 860 Shutdown-Hooks registrieren 750 volatile 806 von Programm aus beenden 752 virtual-Methoden 389 Virtuelle Methoden siehe Überschreibung Visual J++ 41 void 240 volatile 806
1224
Von Neumann 206 Vorzeichen 155
W ■■■■■■■■■■ Wagenrücklauf-Zeichen 136 Wahrheitswerte 136 wait() (Thread) 809 waitFor() (Process) 751 WAN (wide area network) 967 Warteschlangen (Queue) Eigenschaften 773 java.util.ArrayDeque 774 java.util.LinkedList 773 java.util.PriorityQueue 774 Methoden 773, 774, 775 Warteschleifen 214 WeakHashMap 778 Webbrowser siehe Browser Webservices 1129 Annotationen 1132 aufrufen 1134 definieren 1130 Funktionsweise 1129 veröffentlichen 1133 WSDL 1129 wsgen 1133 wsimport 1135 Wechselschalter 526, 532 well-formed (XML) 1107 Werttypen 116 Vergleiche 184 versus Referenztypen 314 Zuweisung 116 while 198, 199 Whitespace 730 Window 476 windowActivated() (WindowListener) 500 WindowAdapter 500 windowClosed() (WindowListener) 500 windowClosing 510 windowClosing() (WindowListener) 474, 500 windowDeactivated() (WindowListener) 500 windowDeiconified() (WindowListener) 500 WindowEvent 499 windowIconified() (WindowListener) 500 WindowListener 500 windowOpened() (WindowListener) 500 Windows JDK – Installation 84 – PATH-Umgebungsvariable setzen 86
Index
X ■■■■■■■■■■ XML 1103 CDATA 1106 Dokumente 1104 Dokumenttyp-Deklarationen (DTD) 1107, 1108 – Attributdeklarationen 1109 – Elementdeklarationen 1109 – Festlegung 1110
DOM 1117 – auf Knoten und Daten zugreifen 1118 – DOM-Baum einlesen 1117 – DOM-Baum schreiben 1122 Element 1104 Entitäten 1106 Groß- und Kleinschreibung 1104 JDOM 1125 – Installation 1126 – JAR-Archiv 1126 – Programmierung 1126 Kommentare 1107 Namensräume 1111 Processing Instructions 1107 Ressourcendateien 902 SAX 1112 – Callback-Methoden implementieren 1113 – Parser erzeugen 1112 Schema 1107, 1110 Tags 1103 Textinhalt 1106 wohlgeformt 1107 XML-Deklaration 1106 XML-Dokumente lesen 1111 XSLT 1124
Y ■■■■■■■■■■ yield() (Thread) 801
Z ■■■■■■■■■■ Zahlen Ausgabe 452, 872 Binärzahlen 1169 Exponentialschreibweise 124 Genauigkeit 131, 325 Gleitkomma (Dezimalzahlen) 128 Hexadezimalzahlen 127, 1170 Integer (ganze Zahlen) 125 Zufallszahlen 165 Zahlensysteme 1169 Binärsystem 1169 Dezimalsystem 1169 Hexadezimalsystem 1170 Umrechnung 1169 Zeichenketten 727 Zeichenketten siehe Strings
1225
Index
MS-DOS-Eingabeaufforderung 48 Notepad-Editor 91 Windows (Look&Feel) 519 Words 1170 Workstation 36 Wörterbücher (Map) 776 Eigenschaften 777 Hashing 777 java.util.EnumMap 778 java.util.HashMap 778 java.util.Hashtable 778 java.util.LinkedHashMap 778 java.util.Map 777 java.util.TreeMap 778, 786 java.util.WeakHashMap 778 Ladefaktor 777 Methoden 777, 778 Rehashing 777 wrap() (ByteBuffer) 882, 886 Wrapper-Klassen 145 write() (DatagramChannel) 1000 write() (DataOutputStream) 849 write() (FileChannel) 881 write() (ImageIO) 604 write() (JTextComponent) 620, 636 write() (OutputStream) 840 write() (RandomAccessFile) 844 write() (SocketChannel) 998 write() (SourceDataLine) 710 writeExternal() (Externalizable) 862 writeObject() (ObjectOutputStream) 858, 861 Writer 838 writer() (Console) 875 writeTo() (ByteArrayOutputStream) 851 writeUTF() (DataOutputStream) 849 writeUTF() (ObjectOutputStream) 858 writeUTF() (RandomAccessFile) 844 writeXxx() (ObjectOutputStream) 858 writeXxx() (RandomAccessFile) 844 WSDL 1129 wsgen 1133 wsimport 1135
Index
Zeichenkodierung 728, 846, 847 abfragen 846 ändern 847 Code Points 730 ISO-8859-1 847, 884 New Java I/O 884 URLs 1004 UTF-8 1004 Zeichensätze 727 Codierung 728 Definition 728 OEM-Zeichensatz der Konsole 134, 875 UCS 728 Unicode 133, 728 Zeichentyp (char) 133 Escape-Sequenzen 135 Unicode 133, 134 Zeichnen 571 Zeichnen siehe Grafik Zeit- und Datumsangaben 741 aktuelle Datumsangabe erzeugen 742 ausgeben 743 beliebige Datumsangaben erzeugen 744 Greenwich Mean Time (GMT) 741 Kalender 742 – gregorianischer 742 – julianischer 742 Sonnenzeit 741 Sternzeit 741
1226
Universalzeit, coordinated (UTC) 742 vergleichen 743 Zeitmessung 743, 752 Zeitgeber 753 auszuführende Aufgabe registrieren 753 erzeugen 753 stoppen 753 Zeitmessung 743, 752 Zeitscheibenverfahren 817 Zertifikate 1092 Zufallszahlen 165, 201 Zugriffsspezifizierer bei der Überschreibung 385 bei der Vererbung 361 für Klassen 219, 256 für Klassenelemente 220, 256 private 254 protected 254, 363 public 254 Standardzugriff 255 Übersicht 365 Zugriffsebenen 361 Zuweisung 153 Referenztypen 117 Strings 138 Werttypen 116 Zwischenablage 555 in Textkomponenten 623, 642
License Agreement
Sun Microsystems, Inc. Binary Code License Agreement for the JAVA SE DEVELOPMENT KIT (JDK), VERSION 6 SUN MICROSYSTEMS, INC. („SUN“) IS WILLING TO LICENSE THE SOFTWARE IDENTIFIED BELOW TO YOU ONLY UPON THE CONDITION THAT YOU ACCEPT ALL OF THE TERMS CONTAINED IN THIS BINARY CODE LICENSE AGREEMENT AND SUPPLEMENTAL LICENSE TERMS (COLLECTIVELY „AGREEMENT“). PLEASE READ THE AGREEMENT CAREFULLY. BY DOWNLOADING OR INSTALLING THIS SOFTWARE, YOU ACCEPT THE TERMS OF THE AGREEMENT. INDICATE ACCEPTANCE BY SELECTING THE „ACCEPT“ BUTTON AT THE BOTTOM OF THE AGREEMENT. IF YOU ARE NOT WILLING TO BE BOUND BY ALL THE TERMS, SELECT THE „DECLINE“ BUTTON AT THE BOTTOM OF THE AGREEMENT AND THE DOWNLOAD OR INSTALL PROCESS WILL NOT CONTINUE. 1. DEFINITIONS. „Software“ means the identified above in binary form, any other machine readable materials (including, but not limited to, libraries, source files, header files, and data files), any updates or error corrections provided by Sun, and any user manuals, programming guides and other documentation provided to you by Sun under this Agreement. „Programs“ mean Java applets and applications intended to run on the Java Platform, Standard Edition (Java SE) on Java-enabled general purpose desktop computers and servers. 2. LICENSE TO USE. Subject to the terms and conditions of this Agreement, including, but not limited to the Java Technology Restrictions of the Supplemental License Terms, Sun grants you a non-exclusive, non-transferable, limited license without license fees to reproduce and use internally Software complete and unmodified for the sole purpose of running Programs. Additional licenses for developers and/or publishers are granted in the Supplemental License Terms. 3. RESTRICTIONS. Software is confidential and copyrighted. Title to Software and all associated intellectual property rights is retained by Sun and/or its licensors. Unless enforcement is prohibited by applicable law, you may not modify, decompile, or reverse engineer Software. You acknowledge that Licensed Software is not designed or intended for use in the design, construction, operation or maintenance of any nuclear facility. Sun Microsystems, Inc. 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. Additional restrictions for developers and/or publishers licenses are set forth in the Supplemental License Terms. 4. 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. Any implied warranties on the Software are limited to 90 days. Some states do not allow limitations on duration of an implied warranty, so the above may not apply to you. This limited warranty gives you specific legal rights. You may have others, which vary from state to state. 5. 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.
1227
Index
6. 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. Some states do not allow the exclusion of incidental or consequential damages, so some of the terms above may not be applicable to you.
License Agreement
7. 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. Either party may terminate this Agreement immediately should any Software become, or in either party's opinion be likely to become, the subject of a claim of infringement of any intellectual property right. Upon Termination, you must destroy all copies of Software. 8. 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. 9. TRADEMARKS AND LOGOS. You acknowledge and agree as between you and Sun that Sun owns the SUN, SOLARIS, JAVA, JINI, FORTE, and iPLANET trademarks and all SUN, SOLARIS, JAVA, JINI, FORTE, and iPLANETrelated trademarks, service marks, logos and other brand designations („Sun 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 Sun Marks inures to Sun's benefit. 10. 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). 11. 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. 12. 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. 13. 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. SUPPLEMENTAL LICENSE TERMS These Supplemental License Terms add to or modify the terms of the Binary Code License Agreement. Capitalized terms not defined in these Supplemental Terms shall have the same meanings ascribed to them in the Binary Code License Agreement . These Supplemental Terms shall supersede any inconsistent or conflicting terms in the Binary Code License Agreement, or in any license contained within the Software. A. Software Internal Use and Development License Grant. Subject to the terms and conditions of this Agreement and restrictions and exceptions set forth in the Software „README“ file incorporated herein by reference, including, but not limited to the Java Technology Restrictions of these Supplemental Terms, Sun grants you a non-exclusive, non-transferable, limited license without fees to reproduce internally and use internally the Software complete and unmodified for the purpose of designing, developing, and testing your Programs. B. License to Distribute Software. Subject to the terms and conditions of this Agreement and restrictions and exceptions set forth in the Software README file, including, but not limited to the Java Technology Restrictions of these Supplemental Terms, Sun grants you a non-exclusive, non-transferable, limited license without fees to reproduce and distribute the Software, provided that (i) you distribute the Software complete and unmodified and only bundled as part of, and for the sole purpose of running, your Programs, (ii) the Programs add significant and primary functionality to the Software, (iii) you do not distribute additional software intended to replace any
1228
License Agreement
component(s) of the Software, (iv) you do not remove or alter any proprietary legends or notices contained in the Software, (v) you only distribute the Software subject to a license agreement that protects Sun's interests consistent with the terms contained in this Agreement, and (vi) 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. C. License to Distribute Redistributables. Subject to the terms and conditions of this Agreement and restrictions and exceptions set forth in the Software README file, including but not limited to the Java Technology Restrictions of these Supplemental Terms, Sun grants you a non-exclusive, non-transferable, limited license without fees to reproduce and distribute those files specifically identified as redistributable in the Software „README“ file („Redistributables“) provided that: (i) you distribute the Redistributables complete and unmodified, and only bundled as part of Programs, (ii) the Programs add significant and primary functionality to the Redistributables, (iii) you do not distribute additional software intended to supersede any component(s) of the Redistributables (unless otherwise specified in the applicable README file), (iv) you do not remove or alter any proprietary legends or notices contained in or on the Redistributables, (v) you only distribute the Redistributables pursuant to a license agreement that protects Sun's interests consistent with the terms contained in the Agreement, (vi) 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. D. Java Technology Restrictions. You may not create, modify, or change the behavior of, or authorize your licensees to create, modify, or change the behavior of, classes, interfaces, or subpackages that are in any way identified as „java“, „javax“, „sun“ or similar convention as specified by Sun in any naming convention designation.
1229
Index
E. Distribution by Publishers. This section pertains to your distribution of the Software with your printed book or magazine (as those terms are commonly used in the industry) relating to Java technology („Publication“). Subject to and conditioned upon your compliance with the restrictions and obligations contained in the Agreement, in addition to the license granted in Paragraph 1 above, Sun hereby grants to you a non-exclusive, nontransferable limited right to reproduce complete and unmodified copies of the Software on electronic media (the „Media“) for the sole purpose of inclusion and distribution with your Publication(s), subject to the following terms: (i) You may not distribute the Software on a stand-alone basis; it must be distributed with your Publication(s); (ii) You are responsible for downloading the Software from the applicable Sun web site; (iii) You must refer to the Software as JavaTM SE Development Kit 6; (iv) The Software must be reproduced in its entirety and without any modification whatsoever (including, without limitation, the Binary Code License and Supplemental License Terms accompanying the Software and proprietary rights notices contained in the Software); (v) The Media label shall include the following information: Copyright 2006, Sun Microsystems, Inc. All rights reserved. Use is subject to license terms. Sun, Sun Microsystems, the Sun logo, Solaris, Java, the Java Coffee Cup logo, J2SE, and all trademarks and logos based on Java are trademarks or registered trademarks of Sun Microsystems, Inc. in the U.S. and other countries. This information must be placed on the Media label in such a manner as to only apply to the Sun Software; (vi) You must clearly identify the Software as Sun's product on the Media holder or Media label, and you may not state or imply that Sun is responsible for any third-party software contained on the Media; (vii) You may not include any third party software on the Media which is intended to be a replacement or substitute for the Software; (viii) You shall indemnify Sun for all damages arising from your failure to comply with the requirements of this Agreement. In addition, you shall defend, at your expense, any and all claims brought against Sun by third parties, and shall pay all damages awarded by a court of competent jurisdiction, or such settlement amount negotiated by you, arising out of or in connection with your use, reproduction or distribution of the Software and/ or the Publication. Your obligation to provide indemnification under this section shall arise provided that Sun: (a) provides you prompt notice of the claim; (b) gives you sole control of the defense and settlement of the claim; (c) provides you, at your expense, with all available information, assistance and authority to defend; and (d) has not compromised or settled such claim without your prior written consent; and (ix) You shall provide Sun with a written notice for each Publication; such notice shall include the following information: (1) title of Publication, (2) author(s), (3) date of Publication, and (4) ISBN or ISSN numbers. Such notice shall be sent to Sun Microsystems, Inc., 4150 Network Circle, M/S USCA12-110, Santa Clara, California 95054, U.S.A , Attention: Contracts Administration.
License Agreement
F. Source Code. Software may contain source code that, unless expressly licensed for other purposes, is provided solely for reference purposes pursuant to the terms of this Agreement. Source code may not be redistributed unless expressly provided for in this Agreement. G. Third Party Code. Additional copyright notices and license terms applicable to portions of the Software are set forth in the THIRDPARTYLICENSEREADME.txt file. In addition to any terms and conditions of any third party opensource/freeware license identified in the THIRDPARTYLICENSEREADME.txt file, the disclaimer of warranty and limitation of liability provisions in paragraphs 5 and 6 of the Binary Code License Agreement shall apply to all Software in this distribution. H. Termination for Infringement. Either party may terminate this Agreement immediately should any Software become, or in either party's opinion be likely to become, the subject of a claim of infringement of any intellectual property right. I. Installation and Auto-Update. The Software's installation and auto-update processes transmit a limited amount of data to Sun (or its service provider) about those specific processes to help Sun understand and optimize them. Sun does not associate the data with personally identifiable information. You can find more information about the data Sun collects at http://java.com/data/. For inquiries please contact: Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, California 95054, U.S.A.
1230
Copyright Daten, Texte, Design und Grafiken dieses eBooks, sowie die eventuell angebotenen eBook-Zusatzdaten sind urheberrechtlich geschützt. Dieses eBook stellen wir lediglich als persönliche Einzelplatz-Lizenz zur Verfügung! Jede andere Verwendung dieses eBooks oder zugehöriger Materialien und Informationen, einschliesslich •
der Reproduktion,
•
der Weitergabe,
•
des Weitervertriebs,
•
der Platzierung im Internet, in Intranets, in Extranets,
•
der Veränderung,
•
des Weiterverkaufs
•
und der Veröffentlichung
bedarf der schriftlichen Genehmigung des Verlags. Insbesondere ist die Entfernung oder Änderung des vom Verlag vergebenen Passwortschutzes ausdrücklich untersagt! Bei Fragen zu diesem Thema wenden Sie sich bitte an:
[email protected] Zusatzdaten Möglicherweise liegt dem gedruckten Buch eine CD-ROM mit Zusatzdaten bei. Die Zurverfügungstellung dieser Daten auf unseren Websites ist eine freiwillige Leistung des Verlags. Der Rechtsweg ist ausgeschlossen. Hinweis Dieses und viele weitere eBooks können Sie rund um die Uhr und legal auf unserer Website
http://www.informit.de herunterladen