Das Java 6 Codebook

Dirk Louis, Peter Müller

Das Java 6 Codebook

Bibliografische Information Der Deutschen Bibliothek Die Deutsche Bibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über <http://dnb.ddb.de> abrufbar.

Die Informationen in diesem Produkt werden ohne Rücksicht auf einen eventuellen Patentschutz veröffentlicht. Warennamen werden ohne Gewährleistung der freien Verwendbarkeit benutzt. Bei der Zusammenstellung von Texten und Abbildungen wurde mit größter Sorgfalt vorgegangen. Trotzdem können Fehler nicht vollständig ausgeschlossen werden. Verlag, Herausgeber und Autoren können für fehlerhafte Angaben und deren Folgen weder eine juristische Verantwortung noch irgendeine Haftung übernehmen. Für Verbesserungsvorschläge und Hinweise auf Fehler sind Verlag und Herausgeber dankbar. Alle Rechte vorbehalten, auch die der fotomechanischen Wiedergabe und der Speicherung in elektronischen Medien. Die gewerbliche Nutzung der in diesem Produkt gezeigten Modelle und Arbeiten ist nicht zulässig.

Fast alle Hardware- und Softwarebezeichnungen 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. Die Einschrumpffolie – zum Schutz vor Verschmutzung – ist aus umweltverträglichem und recyclingfähigem PE-Material.

10 9 8 7 6 5 4 3 2 1 09 08 07 ISBN 978-3-8273-2465-8

© 2007 by Addison-Wesley Verlag,

ein Imprint der Pearson Education Deutschland GmbH, Martin-Kollar-Straße 10–12, D-81829 München/Germany Alle Rechte vorbehalten Korrektorat: Petra Alm Lektorat: Brigitte Bauer-Schiewek, bbauer@pearson.de Herstellung: Elisabeth Prümm, epruemm@pearson.de Satz: Kösel, Krugzell (www.KoeselBuch.de) Umschlaggestaltung: Marco Lindenbeck, webwo GmbH (mlindenbeck@webwo.de) Druck und Verarbeitung: Kösel, Krugzell (www.KoeselBuch.de) Printed in Germany

Inhaltsverzeichnis
Teil I Einführung
Über dieses Buch

13
15

Teil II Rezepte
Zahlen und Mathematik
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 Gerade Zahlen erkennen Effizientes Multiplizieren (Dividieren) mit Potenzen von 2 Primzahlen erzeugen Primzahlen erkennen Gleitkommazahlen auf n Stellen runden Gleitkommazahlen mit definierter Genauigkeit vergleichen Strings in Zahlen umwandeln Zahlen in Strings umwandeln Ausgabe: Dezimalzahlen in Exponentialschreibweise Ausgabe: Zahlenkolonnen am Dezimalzeichen ausrichten Ausgabe in Ein- oder Mehrzahl (Kongruenz) Umrechnung zwischen Zahlensystemen Zahlen aus Strings extrahieren Zufallszahlen erzeugen Ganzzahlige Zufallszahlen aus einem bestimmten Bereich Mehrere, nicht gleiche Zufallszahlen erzeugen (Lottozahlen) Trigonometrische Funktionen Temperaturwerte umrechnen (Celsius <-> Fahrenheit) Fakultät berechnen Mittelwert berechnen Zinseszins berechnen Komplexe Zahlen Vektoren Matrizen Gleichungssysteme lösen Große Zahlen beliebiger Genauigkeit

17
19 19 20 22 24 25 26 30 34 37 44 46 49 51 53 54 56 56 57 59 60 62 72 78 90 91
Kapiteltext Kapiteltext Kapiteltext Kapiteltext Kapiteltext Kapiteltext

19

Strings
27 28 29 30 31 In Strings suchen In Strings einfügen und ersetzen Strings zerlegen Strings zusammenfügen Strings nach den ersten n Zeichen vergleichen

95
95 98 100 101 102

Beispiel für eine zweizeilige Überschrift

Textgestaltung

Textgestaltung

6

>> Inhaltsverzeichnis

Beispiel für eine zweizeilige Überschrift

32 33 34 35 36 37 38

Zeichen (Strings) vervielfachen Strings an Enden auffüllen (Padding) Whitespace am String-Anfang oder -Ende entfernen Arrays in Strings umwandeln Strings in Arrays umwandeln Zufällige Strings erzeugen Wortstatistik erstellen

106 107 110 111 113 116 118

Datum und Uhrzeit
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 Aktuelles Datum abfragen Bestimmtes Datum erzeugen Datums-/Zeitangaben formatieren Wochentage oder Monatsnamen auflisten Datumseingaben einlesen und auf Gültigkeit prüfen Datumswerte vergleichen Differenz zwischen zwei Datumswerten berechnen Differenz zwischen zwei Datumswerten in Jahren, Tagen und Stunden berechnen Differenz zwischen zwei Datumswerten in Tagen berechnen Tage zu einem Datum addieren/subtrahieren Datum in julianischem Kalender Umrechnen zwischen julianischem und gregorianischem Kalender Ostersonntag berechnen Deutsche Feiertage berechnen Ermitteln, welchen Wochentag ein Datum repräsentiert Ermitteln, ob ein Tag ein Feiertag ist Ermitteln, ob ein Jahr ein Schaltjahr ist Alter aus Geburtsdatum berechnen Aktuelle Zeit abfragen Zeit in bestimmte Zeitzone umrechnen Zeitzone erzeugen Differenz zwischen zwei Uhrzeiten berechnen Differenz zwischen zwei Uhrzeiten in Stunden, Minuten, Sekunden berechnen Präzise Zeitmessungen (Laufzeitmessungen) Uhrzeit einblenden

121
121 123 125 128 130 131 133 134 140 141 142 143 144 148 158 159 160 161 163 164 165 168 169 172 174

Kapiteltext

Kapiteltext

Kapiteltext

Kapiteltext

Kapiteltext

System
64 65 66 67 68 69 Umgebungsvariablen abfragen Betriebssystem und Java-Version bestimmen Informationen zum aktuellen Benutzer ermitteln Zugesicherte Umgebungsvariablen System-Umgebungsinformationen abrufen INI-Dateien lesen

179
179 180 181 182 183 184

Kapiteltext

Ein- und Ausgabe (IO)
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 Auf die Konsole (Standardausgabe) schreiben Umlaute auf die Konsole (Standardausgabe) schreiben Von der Konsole (Standardeingabe) lesen Passwörter über die Konsole (Standardeingabe) lesen Standardein- und -ausgabe umleiten Konsolenanwendungen vorzeitig abbrechen Fortschrittsanzeige für Konsolenanwendungen Konsolenmenüs Automatisch generierte Konsolenmenüs Konsolenausgaben in Datei umleiten Kommandozeilenargumente auswerten Leere Verzeichnisse und Dateien anlegen Datei- und Verzeichniseigenschaften abfragen Temporäre Dateien anlegen Verzeichnisinhalt auflisten Dateien und Verzeichnisse löschen Dateien und Verzeichnisse kopieren Dateien und Verzeichnisse verschieben/umbenennen Textdateien lesen und schreiben Textdatei in String einlesen Binärdateien lesen und schreiben Random Access (wahlfreier Zugriff) Dateien sperren CSV-Dateien einlesen CSV-Dateien in XML umwandeln ZIP-Archive lesen ZIP-Archive erzeugen Excel-Dateien schreiben und lesen PDF-Dateien erzeugen

211
211 212 214 216 216 219 219 222 225 230 230 232 234 237 238 239 241 245 245 248 249 250 256 258 266 269 272 274 278

Kapiteltext

Kapiteltext

Kapiteltext

Kapiteltext

Kapiteltext

Kapiteltext

70 71 72 73 74 75 76 77 78 79 80 81 82 83

INI-Dateien schreiben INI-Dateien im XML-Format schreiben Externe Programme ausführen Verfügbaren Speicher abfragen Speicher für JVM reservieren DLLs laden Programm für eine bestimmte Zeit anhalten Timer verwenden TimerTasks gesichert regelmäßig ausführen Nicht blockierender Timer Timer beenden Auf die Windows-Registry zugreifen Abbruch der Virtual Machine erkennen Betriebssystem-Signale abfangen

187 188 189 191 192 192 197 197 199 200 201 201 206 208

Beispiel für eine zweizeilige Überschrift

Textgestaltung

>> Inhaltsverzeichnis

7

Textgestaltung

8

>> Inhaltsverzeichnis

GUI
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 GUI-Grundgerüst Fenster (und Dialoge) zentrieren Fenstergröße festlegen (und gegebenenfalls fixieren) Minimale Fenstergröße sicherstellen Bilder als Fensterhintergrund Komponenten zur Laufzeit instanzieren Komponenten und Ereignisbehandlung Aus Ereignismethoden auf Fenster und Komponenten zugreifen Komponenten in Fenster (Panel) zentrieren Komponenten mit Rahmen versehen Komponenten mit eigenem Cursor Komponenten mit Kontextmenü verbinden Komponenten den Fokus geben Die Fokusreihenfolge festlegen Fokustasten ändern Eingabefelder mit Return verlassen Dialoge mit Return (oder Esc) verlassen Transparente Schalter und nichttransparente Labels Eingabefeld für Währungsangaben (inklusive InputVerifier) Eingabefeld für Datumsangaben (inklusive InputVerifier) Drag-and-Drop für Labels Datei-Drop für JTextArea-Komponenten (eigener TransferHandler) Anwendungssymbol einrichten Symbole für Symbolleisten Menüleiste (Symbolleiste) aus Ressourcendatei aufbauen Befehle aus Menü und Symbolleiste zur Laufzeit aktivieren und deaktivieren Menü- und Symbolleiste mit Aktionen synchronisieren Statusleiste einrichten Hinweistexte in Statusleiste Dateien mit Datei-Dialog (inklusive Filter) öffnen Dateien mit Speichern-Dialog speichern Unterstützung für die Zwischenablage Text drucken Editor-Grundgerüst Look&Feel ändern Systemtray unterstützen Splash-Screen anzeigen Registerreiter mit Schließen-Schaltern (JTabbedPane)

287
287 292 295 296 298 300 303 310 312 315 318 319 322 323 326 329 330 332 336 342 347 349 356 357 359 370 371 377 382 385 391 398 400 410 410 414 417 419

Kapiteltext

Kapiteltext

Kapiteltext

Kapiteltext

Kapiteltext

Beispiel für eine zweizeilige Überschrift

Grafik und Multimedia
151 152 153 Mitte der Zeichenfläche ermitteln Zentrierter Text In den Rahmen einer Komponente zeichnen

425
425 426 428

Kapiteltext

154 155 156 157 158 159 160 161 162 163 164 165 166 167 168

Zeichnen mit unterschiedlichen Strichstärken und -stilen Zeichnen mit Füllmuster und Farbverläufen Zeichnen mit Transformationen Verfügbare Schriftarten ermitteln Dialog zur Schriftartenauswahl Text mit Schattenwurf zeichnen Freihandzeichnungen Bilder laden und anzeigen Bilder pixelweise bearbeiten (und speichern) Bilder drehen Bilder spiegeln Bilder in Graustufen darstellen Audiodateien abspielen Videodateien abspielen Torten-, Balken- und X-Y-Diagramme erstellen

431 433 436 439 440 443 445 448 458 462 464 466 467 471 475

Reguläre Ausdrücke und Pattern Matching
169 170 171 172 173 174 175 176 177 Syntax regulärer Ausdrücke Überprüfen auf Existenz Alle Treffer zurückgeben Mit regulären Ausdrücken in Strings ersetzen Anhand von regulären Ausdrücken zerlegen Auf Zahlen prüfen E-Mail-Adressen auf Gültigkeit prüfen HTML-Tags entfernen RegEx für verschiedene Daten

481
481 484 486 487 488 490 493 495 498
Kapiteltext Kapiteltext Kapiteltext Kapiteltext Kapiteltext

Datenbanken
178 179 180 181 182 183 184 185 186 187 188 Datenbankverbindung herstellen Connection-Pooling SQL-Befehle SELECT, INSERT, UPDATE und DELETE durchführen Änderungen im ResultSet vornehmen PreparedStatements ausführen Stored Procedures ausführen BLOB- und CLOB-Daten Mit Transaktionen arbeiten Batch-Ausführung Metadaten ermitteln Datenbankzugriffe vom Applet

501
501 503 505 508 509 510 512 515 516 518 521

Netzwerke und E-Mail
189 190 191 192 IP-Adressen ermitteln Erreichbarkeit überprüfen Status aller offenen Verbindungen abfragen E-Mail senden mit JavaMail

525
525 526 529 532

Kapiteltext

Beispiel für eine zweizeilige Überschrift

Textgestaltung

>> Inhaltsverzeichnis

9

Textgestaltung

10

>> Inhaltsverzeichnis

193 194 195 196 197 198 199 200 201 202

E-Mail mit Authentifizierung versenden HTML-E-Mail versenden E-Mail als multipart/alternative versenden E-Mail mit Datei-Anhang versenden E-Mails abrufen Multipart-E-Mails abrufen und verarbeiten URI – Textinhalt abrufen URI – binären Inhalt abrufen Senden von Daten an eine Ressource Mini-Webserver

535 537 540 543 545 550 554 555 557 559

Beispiel für eine zweizeilige Überschrift

XML
Kapiteltext

565
Sonderzeichen in XML verwenden Kommentare Namensräume CDATA-Bereiche XML parsen mit SAX XML parsen mit DOM XML-Dokumente validieren XML-Strukturen mit Programm erzeugen XML-Dokument formatiert ausgeben XML-Dokument formatiert als Datei speichern XML mit XSLT transformieren 565 565 566 567 567 571 575 578 580 582 584

203 204 205 206 207 208 209 210 211 212 213

Kapiteltext

Internationalisierung
Kapiteltext

589
589 592 593 597 599 600 601 604 605 607 611 614 616 618

Kapiteltext

214 215 216 217 218 219 220 221 222 223 224 225 226 227

Lokale einstellen Standardlokale ändern Verfügbare Lokalen ermitteln Lokale des Betriebssystems ändern Strings vergleichen Strings sortieren Datumsangaben parsen und formatieren Zahlen parsen und formatieren Währungsangaben parsen und formatieren Ressourcendateien anlegen und verwenden Ressourcendateien im XML-Format Ressourcendateien für verschiedene Lokale erzeugen Ressourcendatei für die Lokale des aktuellen Systems laden Ressourcendatei für eine bestimmte Lokale laden

Kapiteltext

Threads
228 229 230 231 Threads verwenden Threads ohne Exception beenden Eigenschaften des aktuellen Threads Ermitteln aller laufenden Threads

623
623 624 627 628

Kapiteltext

232 233 234 235 236 237 238 239 240 241

Priorität von Threads Verwenden von Thread-Gruppen Iterieren über Threads und Thread-Gruppen einer Thread-Gruppe Threads in Swing: SwingWorker Thread-Synchronisierung mit synchronized (Monitor) Thread-Synchronisierung mit wait() und notify() Thread-Synchronisierung mit Semaphoren Thread-Kommunikation via Pipes Thread-Pooling Thread-globale Daten als Singleton-Instanzen

629 631 632 635 639 640 642 644 646 651

Applets
242 243 244 245 246 247 248 Grundgerüst Parameter von Webseite übernehmen Bilder laden und Diashow erstellen Sounds laden Mit JavaScript auf Applet-Methoden zugreifen Datenaustausch zwischen Applets einer Webseite Laufschrift (Ticker)

655
655 661 663 669 672 675 677
Kapiteltext Kapiteltext Kapiteltext Kapiteltext Kapiteltext Kapiteltext

Objekte, Collections, Design-Pattern
249 250 251 252 253 254 255 256 257 258 259 260 261 Objekte in Strings umwandeln – toString() überschreiben Objekte kopieren – clone() überschreiben Objekte und Hashing – hashCode() überschreiben Objekte vergleichen – equals() überschreiben Objekte vergleichen – Comparable implementieren Objekte serialisieren und deserialisieren Arrays in Collections umwandeln Collections in Arrays umwandeln Collections sortieren und durchsuchen Collections synchronisieren Design-Pattern: Singleton Design-Pattern: Adapter (Wrapper, Decorator) Design-Pattern: Factory-Methoden

681
681 683 689 695 699 705 710 711 712 716 718 721 729

Sonstiges
262 263 264 265 266 267 268 269 270 Arrays effizient kopieren Arrays vergrößern oder verkleinern Globale Daten in Java? Testprogramme schreiben Debug-Stufen definieren Code optimieren jar-Archive erzeugen Programme mit Ant kompilieren Ausführbare jar-Dateien mit Ant erstellen

733
733 734 735 738 742 745 746 748 754

Beispiel für eine zweizeilige Überschrift

Textgestaltung

>> Inhaltsverzeichnis

11

Textgestaltung

12

>> Inhaltsverzeichnis

Beispiel für eine zweizeilige Überschrift

271 272 273 274 275 276

Reflection: Klasseninformationen abrufen Reflection: Klasseninformationen über .class-Datei abrufen Reflection: Klassen instanzieren Reflection: Methode aufrufen Kreditkartenvalidierung Statistik

755 760 762 766 768 770

Teil III Anhang
Tabellen
Java Swing Reguläre Ausdrücke SQL Lokale

777
779
779 782 790 796 797

Kapiteltext

Die Java-SDK-Tools
javac – der Compiler java – der Interpreter jar – Archive erstellen javadoc – Dokumentationen erstellen jdb – der Debugger Weitere Tools
Kapiteltext

799
799 801 803 806 807 809

Kapiteltext

Stichwortverzeichnis

811

Lizenzvereinbarungen

825

Kapiteltext

Kapiteltext

Kapiteltext

Teil I Einführung

Über dieses Buch
Wenn Sie glauben, mit dem vorliegenden Werk ein Buch samt Begleit-CD erstanden zu haben, befinden Sie sich im Irrtum. Was Sie gerade in der Hand halten, ist in Wahrheit eine CD mit einem Begleitbuch. Auf der CD finden Sie – nach Themengebieten geordnet – ungefähr 300 Rezepte mit ready-touse Lösungen für die verschiedensten Probleme. Zu jedem Rezept gibt es im Repository den zugehörigen Quelltext (in Form eines Codefragments, einer Methode oder einer Klasse), den Sie nur noch in Ihr Programm zu kopieren brauchen. Wer den Code eines Rezepts im praktischen Einsatz erleben möchte, findet auf der CD zudem zu fast jedem Rezept ein Beispielprogramm, das die Verwendung des Codes demonstriert. Und dann gibt es noch das Buch. Im Buch sind alle Rezepte abgedruckt, beschrieben und mit Hintergrundinformationen erläutert. Es wurde für Programmierer geschrieben, die konkrete Lösungen für typische Probleme des Programmieralltags suchen oder einfach ihren Fundus an nützlichen Java-Techniken und -Tricks erweitern wollen. In diesem Sinne ist das Java-Codebook die ideale Ergänzung zu Ihrem Java-Lehr- oder -Referenzbuch.

Auswahl der Rezepte
Auch wenn dreihundert Rezepte zweifelsohne eine ganz stattliche Sammlung darstellen, so bilden wir uns nicht ein, damit zu jedem Problem eine passende Lösung angeboten zu haben. Dazu ist die Java-Programmierung ein zu weites Feld (und selbst das vereinte Wissen zweier Autoren nicht ausreichend). Wir haben uns aber bemüht, eine gute Mischung aus häufig benötigten Techniken, interessanten Tricks und praxisbezogenen Designs zu finden, wie sie zum Standardrepertoire eines jeden fortgeschrittenen Java-Programmierers gehören sollten. Sollten Sie das eine oder andere unentbehrliche Rezept vermissen, schreiben Sie uns (autoren@carpelibrum.de). Auch wenn wir nicht versprechen können, jede Anfrage mit einem nachgereichten Rezept beantworten zu können, so werden wir zumindest versuchen, Ihnen mit einem Rat oder Hinweis weiterzuhelfen. Auf jeden Fall aber werden wir ihre Rezeptvorschläge bei der nächsten Auflage des Buches berücksichtigen.

Fragen an die Autoren
Trotz aller Sorgfalt lässt es sich bei einem Werk dieses Umfangs 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 an autoren@carpelibrum.de eine Nachricht zukommen zu lassen. Auch für Lob, Anregungen oder Themenwünsche sind wir stets dankbar. Errata werden auf der Website www.carpelibrum.de veröffentlicht. Sollten Sie Fragen zu einem bestimmten Rezept haben, wenden Sie sich bitte direkt an den betreffenden Autor. In den Quelltexten im Ordner Beispiele sind dazu die Namen der Autoren angegeben. Von Ausnahmen abgesehen gilt aber auch die folgende Zuordnung.

de Peter Müller. dirk@carpelibrum. Führen Sie das Programm aus: java Start oder java Program Bei einigen Programmen müssen Sie externe Bibliotheken in Form von jar-Archiven in den CLASSPATH aufnehmen oder mit der Option –cp als Parameter an java übergeben (java -cp xyz. leserfragen@gmx. 3. dirk@carpelibrum. Collections. dirk@carpelibrum. Design-Pattern Sonstiges Autor(en) Dirk Louis.de Dirk Louis. dirk@carpelibrum.java. dirk@carpelibrum.de Peter Müller.java Bei einigen Programmen müssen Sie externe Bibliotheken in Form von jar-Archiven in den CLASSPATH aufnehmen oder mit der Option -cp als Parameter an javac übergeben (javac -cp xyz. leserfragen@gmx.de Peter Müller.de Dirk Louis. leserfragen@gmx. javac Start.de Peter Müller. leserfragen@gmx.de Peter Müller.jar Program. Kopieren Sie das Verzeichnis auf Ihre Festplatte. gehen Sie wie folgt vor: 1.de Dirk Louis.jar Program).de Kompilieren der Buchbeispiele Wenn Sie eines der Beispiele von der CD ausführen und testen möchten.de Dirk Louis.de Dirk Louis.java).und Ausgabe GUI Grafik und Multimedia Reguläre Ausdrücke und Pattern Matching Datenbanken Netzwerke und E-Mail XML Internationalisierung Threads Applets Objekte. Kompilieren Sie die Quelldateien. In der Regel genügt es dem Java-Compiler den Namen der Programmdatei mit der main()Methode zu übergeben. Für Beispielprogramme mit abweichendem Aufruf finden Sie im Verzeichnis des Beispiels eine Readme-Datei mit passendem Aufruf. 2. .java oder javac Program. leserfragen@gmx.java oder Program. Meist heißt die Programmdatei Start. dirk@carpelibrum.16 >> Über dieses Buch Kategorien Zahlen und Mathematik Strings Datum und Uhrzeit System Ein.

und Ausgabe (IO) GUI Grafik und Multimedia RegEx Datenbanken Netzwerke und E-Mail XML International Threads Applets Objekte Sonstiges .Zahlen und Mathematik Strings Datum und Uhrzeit Teil II Rezepte System Ein.

.

Das Wissen um die Art der Kodierung erlaubt einige äußerst effiziente Tricks. } Listing 1: Gerade Zahlen erkennen 2 Effizientes Multiplizieren (Dividieren) mit Potenzen von 2 Wie im Dezimalsystem die Verschiebung der Ziffern um eine Stelle einer Multiplikation bzw. wie man vielleicht annehmen könnte. dass im 2er-Komplement alle geraden Zahlen im untersten Bit eine 0 und alle ungeraden Zahlen eine 1 stehen haben. ist die zu prüfende Zahl gerade. Negative Zahlen haben eine 1 im MSB und ergeben sich. Um eine Integer-Zahl mit 2n zu multiplizieren. Es lässt sich leicht nachvollziehen. /** * Stellt fest. eine bitweise AND-Verknüpfung zwischen der zu prüfenden Zahl und der Zahl 1 durchzuführen. Man kann also leicht an dem untersten Bit (LSB = Least Significant Bit) ablesen. Das oberste Bit (MSB = Most Significant Bit) kodiert das Vorzeichen und ist für positive Zahlen 0. Ist das Ergebnis 0. sondern als nach dem 2er-Komplement kodierte Folgen von Nullen und Einsen. so entspricht im Binärsystem die Verschiebung um eine Stelle einer Multiplikation bzw. Division mit 10 entspricht. Multiplikationen und Divisionen mit Potenzen von 2 können daher mit Hilfe der bitweisen Shift-Operatoren << und >> besonders effizient durchgeführt werden.Zahlen und Mathematik 1 Gerade Zahlen erkennen Wie alle Daten werden auch Integer-Zahlen binär kodiert. jedoch nicht. dass die Rechengesetze trotz Kodierung des Vorzeichens im MSB erhalten bleiben. Division mit 2. ob die übergebene Zahl gerade oder ungerade ist */ public static boolean isEven(long number) { return ((number & 1l) == 0l) ? true : false. indem man alle Bits der korrespondierenden positiven Zahlen gleichen Betrags invertiert und +1 addiert. ob es sich bei einer Integer-Zahl um eine gerade oder ungerade Zahl handelt. muss man ihre Bits einfach nur um n Positionen nach links verschieben: Zahlen . Im 2er-Komplement werden positive Zahlen durch ihre korrespondierenden Binärzahlen dargestellt. Ein einfaches Verfahren ist. als Zahlen im Binärsystem. beispielsweise das Erkennen von geraden Zahlen. Der Vorzug dieser auf den ersten Blick unnötig umständlich anmutenden Kodierung ist.

erwiesen sich die anderen als Inspiration und Herausforderung für Generationen von Mathematikern – und Informatikern. die besonders effizient arbeiten. In der Java-API werden Hashtabellen beispielsweise durch HashMap. Hashtabellen speichern Daten als Schlüssel/Wert-Paare. muss man ihre Bits einfach nur um n Positionen nach rechts verschieben: /** * Dividieren mit Potenz von 2 */ public static long div(long number. aus dem Schlüssel den Index zu berechnen. es aber andererseits unmöglich ist. unter dem der Wert zu finden ist. ob eine gegebene Zahl eine Primzahl ist? Wie kann man eine gegebene Zahl in ihre Primfaktoren zerlegen? Während die erste Frage bereits in der Antike von dem griechischen Mathematiker Euklid beantwortet werden konnte (es gibt unendlich viele Primzahlen). . Eine gute Hashfunktion liefert für jeden Schlüssel einen eigenen Index. Im zweiten Fall wird berücksichtigt. 3 Primzahlen erzeugen Primzahlen sind Zahlen. dass die meisten Hashtabellen-Implementierungen Hashfunktionen1 verwenden. Weniger gute Hashfunktionen liefern für verschiedene Schlüssel den gleichen Index. die noch einmal extra durchsucht werden müssen. HashSet oder Hashtable implementiert. so dass hinter diesen Indizes Wertelisten stehen. Im ersten Fall ist man an der Generierung großer Primzahlen interessiert (und nutzt den Umstand. dass sich das Produkt zweier genügend großer Primzahlen relativ einfach bilden lässt. der direkt zu dem gesuchten Wert führt. } Listing 2: Multiplikation Um eine Integer-Zahl durch 2n zu dividieren. int pos) { return number >> pos.20 Zahlen >> Primzahlen erzeugen /** * Multiplizieren mit Potenz von 2 */ public static long mul(long number. die nur durch sich selbst und durch 1 teilbar sind. int pos) { return number << pos. Aufgabe der Hashfunktion ist es. } Listing 3: Division Beachten Sie. wenn die Kapazität der Hashtabelle eine Primzahl ist. Dieser schlichten Definition stehen einige der schwierigsten und auch fruchtbarsten Probleme der Mathematik gegenüber: Wie viele Primzahlen gibt es? Wie kann man Primzahlen erzeugen? Wie kann man testen. So spielen Primzahlen beispielsweise bei der Verschlüsselung oder bei der Dimensionierung von Hashtabellen eine große Rolle. 1. in angemessener Zeit die Primzahlen aus dem Produkt wieder herauszurechnen). dass bei Shift-Operationen mit << und >> das Vorzeichen erhalten bleibt (anders als bei einer Verschiebung mit >>>)! Die Division mit div() erwies sich trotz des Function Overheads durch den Methodenaufruf als um einiges schneller als die /-Operation.

n <= limit. 3. numbers. Alle eingerahmten oder nicht durchgestrichenen Zahlen sind Primzahlen. BitSet numbers = new BitSet(max). was bedeutet. die noch nicht durchgestrichen wurden. 4.. ++n) if(!numbers. i < max. die die Primzahlen aus einem durch min und max gegebenen Zahlenbereich als LinkedList-Container zurückliefert.>> Zahlen und Mathematik 21 Zahlen Der wohl bekannteste Algorithmus zur Erzeugung von Primzahlen ist das Sieb des Eratosthenes. } Listing 4: Primzahlen erzeugen // anfangs liefern alle Bits false // keine Primzahlen -> auf true setzen Die Methode prüft zuerst.sqrt(max). for(int n = 2. die Zahlen sind noch nicht ausgestrichen.add(i). ++i) if(!numbers. ob der angegebene Bereich überhaupt Primzahlen enthält. import java. 1.set(0). int limit = (int) Math. int max) { if( (min < 0) || (max < 2) || (min > max) ) return null. Eine mögliche Implementierung dieses Algorithmus verwendet die nachfolgend definierte Methode sieve(). numbers. // Primzahlen im gesuchten Bereich zusammenstellen LinkedList<Integer> prims = new LinkedList<Integer>(). .. Dann legt sie einen BitSet-Container zahlen an.set(i). Rahme die 2 ein und streiche alle Vielfachen von 2 durch. Zu guter Letzt werden die Primzahlen zwischen min und max in einen LinkedListContainer übertragen und als Ergebnis zurückgeliefert. for(int i = min. 2. i+=n) numbers. Schreibe alle Zahlen von 2 bis N auf. .set(1).get(i)) prims. In zwei verschachtelten for-Schleifen werden die Nicht-Primzahlen danach ausgestrichen.util.LinkedList. der die Zahlen von 0 bis max repräsentiert.get(n)) for(int i = 2*n. i < max. Anfangs sind die Bits in numbers nicht gesetzt (false). Wiederhole Schritt 2 für alle n mit n <= sqrt(N). return prims. /** * Sieb des Eratosthenes */ public static LinkedList<Integer> sieve(int min.

print(" " + elem). Wenn Sie in einem Programm einen Hashtabellen-Container anlegen wollen.util.. do { l = sieve(min. Jahrhundert Primjahre waren. Dies leistet die Methode getPrim(). benötigen Sie allerdings eine Methode.sieve(1900.372. if(prims == null) System.LinkedList.out. int max = min + 20. dessen Anfangskapazität sich erst zur Laufzeit ergibt. 4 Primzahlen erkennen Das Erkennen von Primzahlen ist eine Wissenschaft für sich – und ein Gebiet. also Zahlen < 9. . lassen sich schnell und effizient ermitteln. import java.out. die ihnen genau eine passende Primzahl zurückliefert.getPrim(min)). Sie übergeben der Methode die gewünschte Mindestanfangskapazität und erhalten die nächsthöhere Primzahl zurück.807 (worin Sie unschwer die größte positive long-Zahl erkennen werden). welche Jahre im 20.HashMap(MoreMath.util.LinkedList. Kleinere Zahlen. } while (l.22 Zahlen >> Primzahlen erkennen Wenn es Sie interessiert... /** * Liefert die nächsthöhere Primzahl zurück */ public static int getPrim(int min) { LinkedList<Integer> l. max += 10.223. 2000). // Primzahlen erzeugen LinkedList<Integer> prims = MoreMath. else for(int elem : prims) System. . auf dem sich vor kurzem (im Jahre 2001) Erstaunliches getan hat. return l.HashMap map = new java. ob sie durch irgendeine kleinere Zahl (ohne Rest) geteilt werden können. max).775.size() == 0).854. .. indem man prüft.util. können Sie diese Methode beispielsweise wie folgt aufrufen: import java.println("Fehler in Sieb-Aufruf").getFirst().036.util. } Listing 5: Die kleinste Primzahl größer n berechnen Die Erzeugung eines HashMap-Containers mit Hilfe von getPrim() könnte wie folgt aussehen: java.

math. Gleitkommazahlen. wie zum Beispiel dem Primtest nach Rabin-Miller. ob eine long-Zahl eine Primzahl ist */ public static boolean isPrim(long n) { if (n <= 1) return false. Den Wert n übergeben Sie der Methode als Argument. Liefert die Methode false zurück. liegt die Wahrscheinlichkeit. ob eine BigInteger-Zahl eine Primzahl ist * (erkennt Primzahl mit nahezu 100%-iger Sicherheit (1 . dass die BigIntegerModulo-Operation recht zeitraubend ist. Leider können wegen der Beschränkung des Datentyps long mit dieser Methode nur relativ kleine Zahlen getestet werden. ist die Methode fertig und liefert false zurück.sqrt(n).9961)) */ . ist n keine Primzahl und die Methode kehrt sofort mit dem Rückgabewert false zurück. (Die Methode testet intern nach Rabin-Miller und Lucas-Lehmer. Gibt es einen Teiler. if ((n & 1l) == 0l) return false.) import java. handelt es sich definitiv um keine Primzahl. Liefert die Methode true zurück. i+=2) if(n % i == 0) return false. ob die zu prüfende Zahl n kleiner als 2 oder gerade ist. return true. mit der Sie BigInteger-Zahlen testen können. dass es sich um eine Primzahl handelt. Gibt es keinen Teiler.bzw. denn abgesehen davon. Um größere Zahlen zu testen. } Listing 6: »Kleine« Primzahlen erkennen Die Methode testet zuerst. So verfügt die Klasse BigInteger über eine Methode isProbablePrime().BigInteger.) In der Praxis ist dieser Weg aber kaum akzeptabel. bei 1 – 1/2n. i < limit. for(long i = 3. geht die Methode in einer Schleife alle ungeraden Zahlen bis sqrt(n) durch und probiert. Hat n die ersten Tests überstanden. Wenn ja. Mangels schneller deterministischer Verfahren werden Primzahlen daher häufig mit Hilfe probabilistischer Verfahren überprüft. (BigInteger und BigDecimal erlauben die Arbeit mit beliebig großen (genauen) Integer. siehe Rezept 26. /** * Stellt fest.>> Zahlen und Mathematik 23 Zahlen /** * Stellt fest. könnte man den obigen Algorithmus für die Klasse BigInteger implementieren. ob n durch die Schleifenvariable i ohne Rest geteilt werden kann. liefert die Methode true zurück. besitzt der Algorithmus wegen der Modulo-Operation in der Schleife von vornherein ein ungünstiges Laufzeitverhalten.1/28 = 0. // Primzahl sind positive Zahlen > 1 // gerade Zahl long limit = (long) Math.

n). dass es seit 2002 ein von den indischen Mathematikern Agrawal. Dieses Verfahren ist einfach. } Abschließend sei noch erwähnt. Das mathematische Runden rundet immer von hinten nach vorne und unterscheidet sich in der Behandlung der 5 als zu rundender Zahl: Folgen auf die 5 noch weitere von 0 verschiedene Ziffern. ansonsten aufgerundet. Ist die 5 durch Abrundung entstanden. Rundet mathematisch (Rückgabetyp double). Ist sie durch Aufrundung entstanden.isProbablePrime(8).rint(double x) Math. wird aufgerundet. mathematisch auf n Stellen genau runden.pow(10. Rundet auf die nächste kleinere ganze Zahl ab (Rückgabetyp double). 3 oder 4. Mit den folgenden Methoden können Sie kaufmännisch bzw. 1.als abgerundet werden. Kayal und Saxena gefundenes deterministisches Polynomialzeitverfahren zum Test auf Primzahlen gibt. /** * Kaufmännisches Runden auf n Stellen */ public static double round(double number. bedarf es dazu eigener Methoden: eine zum mathematischen und eine zum kaufmännischen Runden auf x Stellen. Folgen auf die 5 keine weiteren Ziffern. Ist diese gleich 0. wird so gerundet. da mehr Zahlen auf.n))) / Math. dass die Zahl 8. führt aber zu einer gewissen Unausgewogenheit.round(number * Math. 5 Gleitkommazahlen auf n Stellen runden Die von Math angebotenen Rundungsmethoden runden – wie das Casting in einen Integer-Typ – stets bis zu einem Integer-Wert auf oder ab. Der letzte Punkt führt beispielsweise dazu. während sie beim kaufmännischen Runden mit round() auf 9 aufgerundet wird.pow(10. Rundet kaufmännisch (Rückgabetyp long bzw. Rundungsmethode Cast () Math.5 von rint() auf 8 abgerundet wird. 2. } . Das kaufmännische Runden betrachtet lediglich die erste zu rundende Stelle.24 Zahlen >> Gleitkommazahlen auf n Stellen runden public static boolean isPrim(BigInteger n) { return n. Tabelle 1: Rundungsmethoden Möchte man Dezimalzahlen auf Dezimalzahlen mit einer bestimmten Anzahl Nachkommastellen runden. wird aufgerundet. int n) { return (Math.round(float x) Math.round(double x) Math. dass die vorangehende Ziffer gerade wird.ceil(double x) Math.floor(double x) Beschreibung Rundet immer ab. Rundet auf die nächste größere ganze Zahl auf (Rückgabetyp double). wird ab-. int). wird abgerundet.

double number = 12. Abbildung 1: Kaufmännisches und mathematisches Runden auf drei Stellen 6 Gleitkommazahlen mit definierter Genauigkeit vergleichen Gleitkommazahlen können zwar sehr große oder sehr kleine Zahlen speichern. runden das Ergebnis mit round() bzw.0 ab! Um mit Rundungsfehlern behaftete Gleitkommazahlen korrekt zu vergleichen. } 25 Zahlen Die Methoden multiplizieren die zu rundende Zahl mit 10n.rint(number * Math.pow(10. sieben und den Datentyp double auf ungefähr zehn signifikante Stellen ein. rint() und dividieren das Ergebnis anschließend durch 10n.>> Zahlen und Mathematik /** * Mathematisches Runden auf n Stellen */ public static double rint(double number. So schränkt der zur Verfügung stehende Speicherplatz den Datentyp float auf ca. if (number == 0. number -= 0. So ergibt der Vergleich in dem folgenden Codefragment wegen Rundungsfehlern in der Zahlendarstellung nicht die erwartete Ausgabe »gleich Null«.123456.) Gravierende Fehler können allerdings entstehen. Kommt es bei einer Berechnung nicht auf extreme Genauigkeit an.n). so entstehen Rundungsfehler.0) System. number -= 12.n))) / Math.println("gleich Null").123456. int n) { return (Math. wenn man Gleitkommazahlen mit Rundungsfehlern vergleicht. die mit einer gewissen Toleranz (epsilon) arbeitet: . Dabei weicht number nur minimal von 0. ob diese 95. stören die Rundungsfehler meist nicht weiter.pow(10.0. wird es nicht darauf ankommen. um wieder die alte Größenordnung herzustellen. um die gewünschte Anzahl Nachkommastellen zu erhalten.out. jedoch nur mit begrenzter Genauigkeit. (Wenn Sie beispielsweise die Wohnfläche einer Wohnung berechnen.45 oder 94. Ergeben sich im Zuge einer Berechnung mit Gleitkommazahlen Zahlen mit mehr signifikanten Stellen oder fließen Literale mit mehr Stellen ein.450000001 qm beträgt. bedarf es daher einer Vergleichsfunktion.

isNaN() bzw.) verfügen jede über eine passende statische parse-Methode (parseShort(). try { number = Integer. Die parse-Methoden der Wrapper-Klassen für die Ganzzahlentypen. if(MoreMath.123456. dass Sie Vergleiche gegen NaN oder Infinity mit Double.abs(a .in). sind überladen.b) < eps. müssen die Strings daher zuerst in einen passenden numerischen Typ wie int oder double umgewandelt werden.println("gleich Null"). wird eine NumberFormatException ausgelöst. 7 Achtung Apropos Vergleiche und Gleitkommazahlen: Denken Sie daran. Um mit den Zahlenwerten rechnen zu können. Int und Long. parseInt().equals(number.isInfinity() durchführen. Kann der String nicht umgewandelt werden. die den ihr übergebenen String in den zugehörigen elementaren Datentyp umwandelt. sind immer Strings – selbst wenn diese Strings Zahlen repräsentieren. über Textkomponenten von GUI-Anwendungen (z. Byte. JTextField) oder aus Textdateien in eine Anwendung eingelesen werden.). 0. } catch(NumberFormatException e) {} . Double etc. Die Umwandlung besteht grundsätzlich aus zwei Schritten: Dem Aufruf einer geeigneten Umwandlungsmethode Der Absicherung der Umwandlung für den Fall.0. so dass Sie neben dem umzuwandelnden String auch die Basis des Zahlensystems angeben können. Integer. number -= 12.B. Short. double eps) { return Math.0. Double. dass der String keine gültige Zahl enthält Für die Umwandlung selbst gibt es verschiedene Möglichkeiten und Klassen: Die parse-Methoden der Wrapper-Klassen Die Wrapper-Klassen zu den elementaren Datentypen (Short. Strings in Zahlen umwandeln Benutzereingaben. number -= 0.26 Zahlen >> Strings in Zahlen umwandeln /** * Gleitkommazahlen mit definierter Genauigkeit vergleichen */ public static boolean equals(double a. 1e10)) System. parseDouble() etc. double b. die über die Konsole (System.out.123456. int base). } Mit dieser Methode kann die »Gleichheit« wie gewünscht festgestellt werden: double number = 12. in dem die Zahl im String niedergeschrieben ist: parseInt(String s.parseInt(str).

wenn der Scanner zuvor geschlossen wurde. nextDouble(). Der Rückgabetyp ist in jedem Fall Number. } catch(ParseException e) {} Die next-Methoden der Klasse Scanner Mit der Klasse Scanner können Eingaben aus Strings. nextBigInteger() etc. ab wo mit dem Parsen des Strings begonnen werden soll.parse(str)). bis sie auf ein Zeichen trifft. Streams oder auch der Konsole (System.) als numerische Typen eingelesen werden. nextInt(). Aber Achtung! Das Dezimalzeichen.>> Zahlen und Mathematik 27 Zahlen Die parse()-Methode von DecimalFormat Die Klasse DecimalFormat wird zwar vorzugsweise zur formatierten Umwandlung von Zahlen in Strings verwendet (siehe Rezept 8). Zeilenumbrüche). import java.text. try { number = (df. DecimalFormat df = new DecimalFormat(). Die Eingabe wird in Tokens zerlegt (Trennzeichen (Delimiter) sind standardmäßig alle Whitespace-Zeichen – also Leerzeichen.hasNextInt()) number = scan. Die eingeparsten Zeichen werden in eine Zahl umgewandelt und als Long-Objekt zurückgeliefert.in). ob ein weiteres Token vorhanden und vom gewünschten Format ist.Scanner. wird ignoriert. gemäß der voreingestellten Lokale der Punkt. Mit den Konvertierungsmethoden von Number (toInt(). import java.ParseException. wird eine ParseException ausgelöst. Dateien. wird das Ergebnis als Double-Objekt zurückgeliefert. Scanner scan = new Scanner(str). mit ihrer parse()-Methode kann aber auch der umgekehrte Weg eingeschlagen werden.DecimalFormat. . toDouble() etc. import java.) kann ein passender elementarer Typ erzeugt werden. um // von der Konsole zu lesen if (scan.in) eingelesen werden. Die von NumberFormat geerbte Version übernimmt allein den umzuwandelnden String. Die parse()-Methode parst die Zeichen im übergebenen String so lange. Satzzeichen). Im Falle eines Fehlers werden folgende Exceptions ausgelöst: InputMismatchException.nextInt().intValue(). Die einzelnen Tokens können mit next() als Strings oder mit den nextTyp-Methoden (nextInt(). Ist der Zahlenwert zu groß oder wurde zuvor für das DecimalFormat-Objekt setParseBigDecimal(true) aufgerufen. // new Scanner(System. die in DecimalFormat definierte Version erhält als zweites Argument eine Positionsangabe vom Typ ParsePosition. Die beiden anderen Exceptions können Sie vermeiden. nextDouble() etc. Letztere wird ausgelöst. Die parse()-Methode ist überladen.util. prüfen. Kann keine Zahl zurückgeliefert werden. NoSuchElementException und IllegalStateException. das sie nicht als Teil der Zahl interpretiert (Buchstabe. etwa weil der String mit einem Buchstaben beginnt. wenn Sie vorab mit next(). Tabulatoren.text. die festlegt.

und liefert das Ergebnis als LongObjekt (bzw.length != 1) { System. parst diesen Zeichen für Zeichen.28 Zahlen >> Strings in Zahlen umwandeln Methode Beschreibung Absicherung Laufzeit (für »154« auf PIII.4« -> 154 »15s4« -> 15 »s154« -> Exception »154« -> 154 »15. import java.out.text.println(" Aufruf: Start <Ganzzahl>").ParseException.4« -> Exception »15s4« -> Exception »s154« -> Exception Übernimmt als Argument den umzuwandelnden String. 2 GHz) < 1 sec Integer. public class Start { public static void main(String[] args) { System.nextInt() Scanner.parse() ParseException ~ 100 sec Scanner.Scanner.text.util.4« -> hasNextInt() ergibt false »15s4« -> hasNextInt() ergibt false »s154« -> hasNextInt() ergibt false NumberFormatException DecimalFormat. Listing 7: Vergleich verschiedener Umwandlungsverfahren .out.DecimalFormat. »154« -> 154 »15. bis das Ende oder ein Nicht-Zahlen-Zeichen erreicht wird. Double) zurück. import java.parseInt() Übernimmt als Argument den umzuwandelnden String und versucht ihn in einen int-Wert umzuwandeln.println(). »154« -> 154 »15. if (args.hasNextInt() ~ 2500 sec Tabelle 2: Vergleich verschiedener Verfahren zur Umwandlung von Strings in Zahlen Mit dem folgenden Programm können Sie Verhalten und Laufzeit der verschiedenen Umwandlungsmethoden auf Ihrem Rechner prüfen: import java.

// Umwandlung mit DecimalFormat number = -1.currentTimeMillis(). (end-start)). // Umwandlung mit Scanner number = -1.currentTimeMillis(). ++i) { Scanner scan = new Scanner(str). number. } long start. start = System. i <= 10000. (end-start)). number.parse(str)).currentTimeMillis(). i <= 10000. "DecimalFormat". // Die umzuwandelnde Zahl als String // Umwandlung mit parseInt() number = -1. "Scanner". try { number = (df. for(int i = 0. i <= 10000. // für die Zeitmessung int number.exit(0).printf("%15s liefert %d nach %5s sec \n".currentTimeMillis().currentTimeMillis(). } end = System.) . number. System. if (scan.out. "parseInt()". System. start = System.nextInt(). } catch(NumberFormatException e) {} } end = System.out. (end-start)).printf("%15s liefert %d nach %5s sec \n".out. ++i) { DecimalFormat df = new DecimalFormat().currentTimeMillis(). } } Listing 7: Vergleich verschiedener Umwandlungsverfahren (Forts. ++i) { try { number = Integer. start = System. for(int i = 0. System.parseInt(str).intValue(). } catch(ParseException e) {} } end = System. String str = args[0].hasNextInt()) number = scan.printf("%15s liefert %d nach %5s sec \n".>> Zahlen und Mathematik 29 Zahlen System. for(int i = 0. end.

0 0. Der intern verwendete Umwandlungsalgorithmus kann dazu führen. dass eine abschließende Null ausgegeben wird.toString() Float.toString() Byte. (printf() eignet sich nur zur Ausgabe auf die Konsole und wird hier nicht weiter behandelt. toString()-Methode Integer.print() und PrintStream. zu den elementarsten Programmieraufgaben überhaupt. Dieser Trick erlaubt es.00099 1e380 * 10 Tabelle 3: Formate der toString()-Methoden Bei Ausgaben mit PrintStream. Für die Wrapper-Klassen zu den numerischen Datentypen ist dies geschehen (siehe Tabelle 3).0 dargestellt. beliebig formatierbaren Umwandlung (für größtmögliche Flexibilität) sowie der von C übernommenen formatierten Ausgabe mit printf(). Zahlen außerhalb des Bereichs von 10-3 und 107 werden in Exponentialschreibweise dargestellt oder als infinity. Java unterstützt den Programmierer dabei mit drei Varianten: der auf toString() basierenden.toString() Short. deren Betrag zwischen 10-3 und 107 liegt. Zahlen mühelos auszugeben oder in Strings einzubauen – sofern man sich mit der Standardformatierung durch die toString()-Methoden zufrieden gibt.toString() Long. die eine Stringdarstellung des aktuellen Objekts zurückliefert. -333. Negative Zahlen beginnen mit einem Minuszeichen. Null wird als 0. werden als Zahl mit Nachkommastellen dargestellt. die Umwandlung von Strings in Zahlen. Abgeleitete Klassen können die Methode überschreiben. 123 -9000 Wie für Integer. Es wird immer mindestens eine Nachkommastelle ausgegeben. . Für eine Beschreibung der Methode siehe Lehrbücher zu Java oder die Java-API-Referenz. Die Implementierung von Object liefert einen String des Aufbaus klassenname@hashCodeDesObjekts zurück.30 Zahlen >> Zahlen in Strings umwandeln 8 Zahlen in Strings umwandeln Die Umwandlung von Zahlen in Strings gehört wie ihr Pendant.toString() Double.0010 9.) Zahlen in Strings umwandeln mit toString() Wie Sie wissen. um sinnvollere Stringdarstellungen ihrer Objekte zurückzugeben.println() oder bei Stringkonkatenationen mit dem +-Operator wird für primitive numerische Daten intern automatisch ein Objekt der zugehörigen Wrapper-Klasse erzeugt und deren toString()-Methode aufgerufen. der auf NumberFormat und DecimalFormat basierenden. bestehend aus maximal 32 Ziffern.9E-4 Infinity // // // // -333 0. Zahlen. Negative Zahlen werden mit Vorzeichen dargestellt. aber mit maximal 64 Ziffern. erben alle Java-Klassen von der obersten Basisklasse die Methode toString(). (weitgehend) automatischen Umwandlung (für größtmögliche Bequemlichkeit).001 0.toString() zurückgelieferter String Stringdarstellung der Zahl.

Die eigentliche Zahl wird gemäß dem mittleren Teil formatiert.##0 #. Die Zahl der obligatorischen Ziffern entspricht hier der Mindestzahl an Stellen. \u2030 (Promillesymbol) und \u00A4 (Währungssymbol) enthalten. der formatierte String wird als Ergebnis zurückgeliefert.out. . ein spezielles Negativ-Pattern angehängt. 31 Zahlen Zahlen in Strings umwandeln mit NumberFormat und DecimalFormat Wem die Standardformate von toString() nicht genügen. das angibt. Die Patterns haben folgenden Aufbau: Präfixopt Zahlenformat Suffixopt Präfix und Suffix können neben beliebigen Zeichen.print(number).und Gleitkommazahlen können nach folgenden Schemata aufgebaut werden: #. wie das Objekt Zahlen formatiert.00# Die Vorkommastellen können durch das Tausenderzeichen gruppiert werden. die Summe aus obligatorischen und optionalen Ziffern der Maximalzahl an Stellen. auch die Symbole % (Prozentsymbol). System. Die zu formatierende Zahl wird als Argument übergeben. mehr als ein Tausenderzeichen zu setzen. Die eigentliche Formatierung erfolgt durch Aufruf der format()-Methode des Objekts. Es ist unnötig. Die maximale Anzahl Stellen im Vorkommateil ist unbegrenzt. die unverändert ausgegeben werden. Optional kann sich an beide Formate die Angabe eines Exponenten anschließen (siehe Rezept 9). optionale Stellen müssen lediglich zum Setzen des Tausenderzeichens angegeben werden. Die Klasse DecimalFormat arbeitet mit Patterns (Mustern). da bei mehreren Tausenderzeichen die Anzahl der Stellen pro Gruppe gleich der Anzahl Stellen zwischen dem letzten Tausenderzeichen und dem Ende des ganzzahligen Teils ist (in obigem Beispiel 3). der kann auf die abstrakte Klasse NumberFormat und die von ihr abgeleite Klasse DecimalFormat zurückgreifen. es wird dem Pattern für die positiven Zahlen mittels .##0. Negative Zahlen werden standardmäßig durch Voranstellung des Minuszeichens gebildet. E Bedeutung obligatorische Ziffer optionale Ziffer Dezimalzeichen Tausenderzeichen Minuszeichen Exponentialzeichen Tabelle 4: Symbole für DecimalFormat-Patterns Integer. Im Vorkommateil darf rechts von einer obligatorischen Ziffer (0) keine optionale Ziffer mehr folgen.print("Wert der Variablen: " + number). System.>> Zahlen und Mathematik int number = 12.out. . es sei denn. Im Nachkommateil darf rechts von einer optionalen Ziffer keine obligatorische Ziffer mehr folgen. Jedes DecimalFormat-Objekt kapselt intern ein Pattern. der folgende Symbole enthalten kann: Symbol 0 # .

out.) getCurrencyInstance() Format für Preisangaben: #. . System. System. keine Nachkommastellen und Tausenderzeichen nach je drei Stellen) getPercentInstance() Format für Prozentangaben: #. number = 12345.DecimalFormat. Währungssymbol etc.### (= Zahl mit mindestens einer Stelle.6789. nf = NumberFormat.. keine Nachkommastellen.format(number)).getNumberInstance().getPercentInstance().##0.. // Ausgabe: 3.##0.format(number)). import java..und Währungszeichen) Tabelle 5: Factory-Methoden der Klasse NumberFormat import java. werden gemäß der aktuellen Lokale der JVM gesetzt.32 Zahlen >> Zahlen in Strings umwandeln Landesspezifische Symbole wie Tausenderzeichen.59 Statt eigene Formate zu definieren. können Sie sich auch von den statischen Methoden der Klasse NumberFormat vordefinierte DecimalFormat-Objekte zurückliefern lassen: NumberFormat-Methode getInstance() getNumberInstance() Liefert Format für beliebige Zahlen: #. maximal drei Nachkommastellen und Tausenderzeichen nach je drei Stellen) getIntegerInstance() Format für Integer-Zahlen: #.println(df. DecimalFormat df = new DecimalFormat("#.out.. System. NumberFormat nf = NumberFormat.out.format(number)). double number = 3344.344.##0.00").print(nf.NumberFormat.679 .345. Tausenderzeichen nach je drei Stellen und abschließendem Leer.##0 (= Zahl mit mindestens einer Stelle.text.print(nf.##0% (= Zahl mit mindestens einer Stelle.00 ¤ (= Zahl mit mindestens einer Stelle.text. // Ausgabe: 30% // Ausgabe: 12.588. Tausenderzeichen nach je drei Stellen und abschließendem Prozentzeichen) (Achtung! Die zu formatierende Zahl wird automatisch mit 100 multipliziert.3. double number = 0. genau zwei Nachkommastellen. .

format(number)).6789.setMaximumFractionDigits(2).NumberFormat. Minimale Anzahl Stellen im Integer-Teil. lohnt sich unter Umständen die Definition eigener vordefinierter Formate. Die folgenden Definitionen gestatten die schnelle Formatierung von Gleitkommazahlen gemäß US-amerikanischer Gepflogenheiten.text. import java. beispielsweise in Form von static final-Konstanten. import java.68 Meter Eigene. void setGroupingUsed(boolean opt) void setMaximumFractionDigits(int n) void setMaximumIntegerDigits(int n) void setMinimumFractionDigits(int n) void setMinimumIntegerDigits(int n) void setMultiplier(int n) setNegativePrefix(String setNegativeSuffix(String setPositivePrefix(String setPositiveSuffix(String new) new) new) new) Tabelle 6: Set-Methoden von DecimalFormat (die hervorgehobenen Methoden sind auch für NumberFormat definiert) import java. nf. System.DecimalFormat.setGroupingUsed(false).Locale.setPositiveSuffix(" Meter").getNumberInstance(). double number = 12345. // Ausgabe: 12345. Wird true übergeben.text. . mit maximal drei Nachkommastellen und je nach ausgewählter Konstante mit oder ohne Gruppierung.util. ob die Vorkommastellen gruppiert werden sollen.und Promille-Darstellung. Maximale Anzahl Stellen im Nachkommateil.>> Zahlen und Mathematik 33 Zahlen Formatierungsobjekte anpassen Die von einem DecimalFormat-Objekt vorgenommene Formatierung kann jederzeit durch Aufruf der entsprechenden set-Methoden angepasst werden. Minimale Anzahl Stellen im Nachkommateil. die mittels eines static-Blocks konfiguriert werden. Setzt Präfixe und Suffixe der Patterns für negative bzw.text. NumberFormat nf = NumberFormat.. vordefinierte Formate Wenn Sie an verschiedenen Stellen immer wieder dieselben Formatierungen benötigen. wird das Dezimalzeichen auch am Ende von Integer-Zahlen angezeigt. nf. if (nf instanceof DecimalFormat) ((DecimalFormat) nf). import java. public class MoreMath { .print(nf. Methode void setCurrency(Currency c) void setDecimalSeparatorAlwaysShown(boolean opt) void setGroupingSize(int n) Beschreibung Ändert die zu verwendende Währung. Legt fest.NumberFormat. Maximale Anzahl Stellen im Integer-Teil. positive Zahlen. Anzahl Stellen pro Gruppe.. Faktor für Prozent.out.

out.getNumberInstance(Locale. Ausgabe: Dezimalzahlen in Exponentialschreibweise Es mag verwundern.US). Lediglich printf() bietet Unterstützung für die Formatierung in Exponentialschreibweise (Konvertierungssymbol %e).getNumberInstance() bemühen. NFUS_NOGROUP = NumberFormat. Zahlenformat und Suffix.US).679 9 H in we i s Mehr zur landesspezifischen Formatierung in der Kategorie »Internationalisierung«.6789 System.345.setGroupingUsed(false). } Ausgabe: 12. aber die Ausgabe von Dezimalzahlen in Exponentialschreibweise stellt in Java insofern ein Problem dar.getNumberInstance(Locale. NFUS_NOGROUP.679 12345. (Mehr zu DecimalFormat-Patterns in Rezept 8).. System.println(MoreMath.out.0#E0 .format(number)). Wenn Sie NumberFormat. Will man also Dezimalzahlen in Exponentialschreibweise darstellen. Eine vordefinierte NumberFormat-Instanz für die Exponentialschreibweise gibt es nicht. doch eignet sich printf() nur für die Konsolenausgabe.NFUS. DecimalFormat-Patterns für die Exponentialdarstellung Patterns für die Exponentialdarstellung bestehen wie jedes DecimalFormat-Pattern aus Präfix.println(MoreMath.format(number)).34 Zahlen >> Ausgabe: Dezimalzahlen in Exponentialschreibweise public static final NumberFormat NFUS.. double number = 12345. sind die Zahlen mal als normale Dezimalbrüche und mal in Exponentialschreibweise formatiert. } // Instanzbildung unterbinden private MoreMath() { } } Aufruf: public static void main(String args[]) { . public static final NumberFormat NFUS_NOGROUP. Das Zahlenformat hat den Aufbau: #0. Wenn Sie für die Umwandlung einer Dezimalzahl in einen String der toString()-Methode vertrauen. erhalten Sie immer einfache Dezimalbrüche. als es (derzeit) keine standardmäßige Unterstützung dafür gibt.NFUS_NOGROUP. muss man für ein geeignetes Pattern ein eigenes DecimalFormat-Objekt erzeugen. static { NFUS = NumberFormat.

Grundsätzlich gilt: Sie geben die maximale und minimale Anzahl Vorkommastellen an und das DecimalFormat-Objekt berechnet den passenden Exponenten. sechs signifikanten Stellen und Exponenten.#E00 String 1. die Vielfache von 3 sind (DFEXP_ENG). import java.3456 Pattern 0. Tausenderzeichen sind nicht erlaubt.5E-01 Enthält der Vorkommateil optionale Stellen (#).3456 12. Die maximale Zahl ist unbeschränkt.#E0 String 1. public class MoreMath { public static final DecimalFormat DFEXP.text. Listing 8: Vordefinierte DecimalFormat-Objekte für die Exponentialdarstellung .2E1 123. dass exakt die vorgegebene Zahl Stellen vor dem Komma erreicht wird. die über den Aufbau des Vorkommateils ausgewählt werden: Besteht der Vorkommateil nur aus obligatorischen Stellen (0).#E0 000.#E0 ##0. Zahl 12. Für die Berechnung des Exponenten gibt es zwei Modelle. Die folgenden Definitionen gestatten die schnelle Formatierung von Gleitkommazahlen in Exponentialschreibweise mit einer Vorkommastelle und sechs signifikanten Stellen (DFEXP).>> Zahlen und Mathematik 35 Zahlen Die Umwandlung eines solchen Patterns in eine formatierte Zahl ist etwas eigentümlich.35E0 123.#E0 ##0. Zahl 12.3456 12.3456 123456 Pattern #. public static final DecimalFormat DFEXP_ENG.DecimalFormat. berechnet DecimalFormat den Exponenten so. die mittels eines static-Blocks konfiguriert werden. Vordefinierte Patterns Wenn Sie an verschiedenen Stellen immer wieder dieselben Formatierungen benötigen.5E-1 123.2E1 12.#E0 000. lohnt sich unter Umständen die Definition eigener vordefinierter Formate.5E3 Die Anzahl signifikanter Stellen in der Mantisse entspricht der Summe aus obligatorischen Vorkomma. Aus diesem Grund kann für den Exponenten auch nur die minimale Anzahl Stellen angegeben werden. ist der Exponent stets ein Vielfaches der Summe an Vorkommastellen. beispielsweise in Form von static final-Konstanten.3456 12.und maximaler Anzahl Nachkommastellen.

int maxDigits. Wer ein kleines E bevorzugt oder den Exponenten stets mit Vorzeichen dargestellt haben möchte (so wie es printf() tut).formatExp(number. false).#####E0").append(MoreString. } public static String formatExp(double number. boolean smallExp. maxStellen..36 Zahlen >> Ausgabe: Dezimalzahlen in Exponentialschreibweise static { DFEXP = new DecimalFormat("0. false). Optional können Sie über Boolesche Argumente zwischen großem und kleinem E und zwischen Plus. maxStellen.charNTimes('#'.#####E0"). boolean smallExp) { return MoreMath. public class MoreMath { . false. Die Methode formatExp() kann Ihnen diese Arbeit abnehmen. muss den von DecimalFormat. int maxStellen.#").DecimalFormat.text. Sie formatiert die übergebene Zahl in Exponentialschreibweise mit einer Vorkommastelle..formatExp(number.und Minuszeichen oder nur Minuszeichen vor dem Exponenten wählen..) Eigene Formatierungsmethode DecimalFormat verwendet zur Kennzeichnung des Exponenten ein großes E und zeigt positive Exponenten ohne Vorzeichen an. import java. pattern. Die maximale Anzahl Nachkommastellen in der Mantisse wird als Argument übergeben. smallExp. /** * Formatierung als Dezimalzahl in Exponentialschreibweise */ public static String formatExp(double number..format() zurückgelieferten String manuell weiterverarbeiten.append("E00"). } . boolean plus) { // Pattern für Exponentialschreibweise erzeugen StringBuilder pattern = new StringBuilder("0. int maxStellen) { return MoreMath.maxDigits-1)). Listing 9: Dezimalzahlen in Exponentialschreibweise . DFEXP_ENG = new DecimalFormat("##0. } public static String formatExp(double number. if(maxDigits > 1) pattern. } Listing 8: Vordefinierte DecimalFormat-Objekte für die Exponentialdarstellung (Forts.

if (smallExp) tmp. '+'). dass sich besagtes Problem durch Übergabe eines FieldPosition-Objekts an die format()-Methode von Number- .toString(). denn neben der Berechnung korrekter Ausgaben ist deren Formatierung natürlich nur zweitrangig. if (plus && Math.>> Zahlen und Mathematik 37 Zahlen // Zahl als String formatieren String str = (new DecimalFormat(pattern. wird das Pluszeichen hinter dem E (bzw. 10 Ausgabe: Zahlenkolonnen am Dezimalzeichen ausrichten Die meisten Programmierer betrachten die Formatierung der Ausgaben als ein notwendiges Übel – sicherlich nicht ganz zu Unrecht. Dann erzeugt sie den gewünschten Formatierer und noch in der gleichen Zeile durch Aufruf der format()Methode die Stringdarstellung der Zahl. die String fehlt.indexOf('E').toString())). Für eine Darstellung mit kleinem e wird das große E im String durch das kleine e ersetzt. wobei sie zur Vervielfachung der optionalen Nachkommastellen die Methode MoreString. // Exponentzeichen und/oder Pluszeichen if (smallExp || (plus && Math. wenn sich dieses notwendige Übel als unnötig kompliziert erweist. sondern wegen der insert()-Methode. boolean smallExp. } else return str. Trotzdem sollte die Formatierung der Ausgaben. In der API-Dokumentation zur Java-Klasse NumberFormat findet sich hierzu der Hinweis. e) eingefügt. return tmp.abs(number) >= 1)) { int pos = str.replace(pos. } } Listing 9: Dezimalzahlen in Exponentialschreibweise (Forts. Mit dem zugehörigen Testprogramm können Sie das Ergebnis verschiedener Formatierungsmöglichkeiten vergleichen. boolean plus) baut zuerst das gewünschte Pattern auf.abs(number) >= 1) tmp. wie beispielsweise bei der Ausrichtung von Zahlenkolonnen am Dezimalzeichen. Für die Nachbearbeitung des Strings wird dieser in ein StringBuilder-Objekt umgewandelt – nicht wegen der Unveränderbarkeit von String-Objekten (die dazu führt. Wurde die Darstellung mit Pluszeichen vor dem Exponent gewünscht und ist der Betrag der Zahl größer oder gleich 1.) Die Methode formatExp(double number. int maxStellen. nicht vernachlässigt werden – nicht einmal dann. dass bei StringManipulationen stets Kopien erzeugt werden). pos+1.charNTimes() aus Rezept 32 aufruft. insbesondere die Präsentation von Ergebnissen.insert(pos+1. "e"). StringBuilder tmp = new StringBuilder(str).format(number).

empfiehlt es sich. in dem das Dezimalzeichen am weitesten hinten liegt.text. untersuchen die beiden fol- genden Abschnitte. Dies geschieht durch Erzeugung einer passenden NumberFormat.DecimalFormat.. Die Methode übernimmt ein double-Array der auszugebenden Zahlen und einen Formatstring für DecimalFormat und liefert die für die Ausgabe aufbereiteten Stringdarstellungen als Array von StringBuffer-Objekten zurück. Hier ist zu beachten. import java. Wie dies konkret aussieht. Die nachfolgend abgedruckte Methode alignAtDecimal() erledigt alle drei Schritte in einem. damit nicht bei der Ausgabe der Platz für die Vorkommastellen der einzelnen Strings fehlt.FieldPosition. 3.38 Zahlen >> Ausgabe: Zahlenkolonnen am Dezimalzeichen ausrichten Abbildung 2: Formatierung von Dezimalzahlen Format (bzw. Die Zahlen müssen in Strings umgewandelt werden. Den einzelnen Strings müssen so viele Leerzeichen vorangestellt werden. die Strings mit den Zahlen in einer Schleife zu durchlaufen und sich an dem String zu orientieren. Die gewünschte Position des Dezimalzeichens muss festgelegt werden. DecimalFormat) lösen lässt. der Sie nur das Zahlen-Array übergeben müssen. 8. 2. .1271 }.. ausgerichtet am Dezimalzeichen. 100. siehe Rezept 8. müssen drei Dinge geschehen: 1. Um diese Zahlenkolonne untereinander. Ausgaben bei proportionaler Schrift Als Beispiel betrachten wir das folgende Zahlen-Array: double[] numbers = { 1230.45. ausgeben zu können. Für die häufig benötigte Ausgabe von Zahlen mit zwei Nachkommastellen gibt es eine eigene überladene Version. dass die gewählte Position nicht zu weit vorne liegt. /** * Array von Strings am Dezimalzeichen ausrichten Listing 10: Ausrichtung am Dezimalzeichen bei proportionaler Schrift .oder DecimalFormat-Instanz und Übergabe an die Methode format(). import java.text. Liegen die auszugebenden Strings bereits zu Beginn der Ausgabe komplett vor. bis ihr Dezimalzeichen an der gewünschten Stelle liegt.

"#. entweder eigenen Code zur Unterstützung verschiedener Lokale zu schreiben oder aber eine feste Lokale vorzugeben und damit auf automatische Adaption an nationale Eigenheiten zu verzichten? Mitnichten. } return strings. int maxDist = 0. die das Komma als Dezimalzeichen verwendet. i < numbers. String format) { DecimalFormat df = new DecimalFormat(format). wenn DecimalFormat gemäß einer Lokale formatiert.) Wie findet diese Methode die Position der Dezimalzeichen? Denkbar wäre natürlich.length]. // nötige Vorarbeiten // Strings initisieren. pad). if (maxDist < charToDecP[i]) maxDist = charToDecP[i].>> Zahlen und Mathematik 39 Zahlen * (Version für proportionale Schrift) */ public static StringBuffer[] alignAtDecimal (double[] numbers) { return alignAtDecimalPoint(numbers.##0.charToDecP[i]]. Sie müssen der format()-Methode lediglich als drittes Argument eine FieldPosition-Instanz übergeben und können sich dann von diesem die Position des Dezimalzeichens zurückliefern lassen.length. zu welchem Zweck die Konstante INTEGER_FIELD übergeben wird.getEndIndex(). for(int i = 0. Doch dieser Ansatz funktioniert natürlich nur. ++n) pad[n] = ' '.insert(0. charToDecP[i] = fpos. StringBuffer[] strings = new StringBuffer[numbers. i < numbers.format(numbers[i]. dass diese eingangs ein FieldPosition-Objekt erzeugt. max. for(int n = 0.00"). strings[i].length. Zahl Vorkommastellen ermitteln for(int i = 0. Für die alignAtDecimal()-Methode sieht dies so aus. (Wenn Sie die ebenfalls vordefinierte Konstante . } Listing 10: Ausrichtung am Dezimalzeichen bei proportionaler Schrift (Forts. strings[i]. n < pad. fpos).length. } // nötige Anzahl Leerzeichen voranstellen char[] pad.length].INTEGER_FIELD). Position des Dezimalpunkts // feststellen. ++i) { pad = new char[maxDist . einfach mit indexOf() nach dem Komma zu suchen. int[] charToDecP = new int[numbers. } public static StringBuffer[] alignAtDecimal (double[] numbers. FieldPosition fpos = new FieldPosition(DecimalFormat. ++i) { strings[i] = new StringBuffer(""). Dieses liefert Informationen über den ganzzahligen Anteil. Lauten die Alternativen demnach. df.

println(" Rendite (%) : " + strings[2]).oder JPanel-Instanz) gezeichnet werden sollen. auf den Nachkommaanteil.40 Zahlen >> Ausgabe: Zahlenkolonnen am Dezimalzeichen ausrichten FRACTION_FIELD übergeben. strings = MoreMath.##0.1271 }. übergeben Sie einfach Ihren eigenen Formatstring. System.out.println(" Bonus : " + strings[1]).out.alignAtDecimal(numbers.out.45. damit sein Dezimalzeichen in einer Höhe mit den Dezimalzeichen der anderen Strings liegt.java strings = MoreMath.println(" Bonus : " + strings[1]). die die Methoden des FieldPositionObjekts zurückliefern.out. System. wird die Position hinter der letzten Ziffer des Vorkommateils zurückgeliefert. können die Strings mit den Zahlendarstellungen nicht durch Einfügen von Leerzeichen ausgerichtet werden. System.out. Ausgaben bei nichtproportionaler Schrift Etwas komplizierter wird es.println(). Da in einer nichtproportionalen Schrift die einzelnen Buchstaben unterschiedliche Breiten haben. wenn die Zahlen in nichtproportionaler Schrift in ein Fenster oder eine Komponente (vorzugsweise eine Canvas.println(" Kapital : " + strings[0]). // aus Start.out. "#. System.) In einer ersten Schleife werden dann die Zahlen mit Hilfe der format()-Methode in Strings umgewandelt und in StringBuffer-Objekten abgespeichert. beziehen sich die Angaben.out. Die Position des Dezimalzeichens wird für jeden String mit Hilfe der FieldPosition-Methode getEndIndex() abgefragt und im Array charToDecP zwischengespeichert. .alignAtDecimal(numbers). StringBuffer[] strings. siehe auch Rezept 8. In der anschließenden. ab welcher x-Koordinate mit dem Zeichnen des Strings zu beginnen ist. dass in allen Strings das Dezimalzeichen maxDist Positionen hinter dem Stringanfang liegt.java double[] numbers = { 1230. System. (Enthält der String kein Dezimalzeichen. System. System.) Gleichzeitig wird in maxDist der größte Abstand von Stringanfang bis Dezimalzeichen festgehalten.println(" Rendite (%) : " + strings[2]).println(" Kapital : " + strings[0]).0######"). Stattdessen muss für jeden String berechnet werden.out.println(). zweiten for-Schleife werden die Strings dann so weit vorne mit Leerzeichen aufgefüllt. System. Der Einsatz der Methode könnte nicht einfacher sein: Sie übergeben ihr das Array der zu formatierenden Zahlen und erhalten die fertigen Strings in Form eines StringBuffer-Arrays zurück: // aus Start. Sagt Ihnen die vorgegebene Formatierung mit zwei Nachkommastellen nicht zu. 8. 100.

g.setFont(new Font("Courier".FontMetrics.toString().FieldPosition. y ). y + 2 * fm. zeichnen. y + fm.paintComponent(g). x.getHeight()). 24)). strings = MoreMath.##0. /** * Array von Strings am Dezimalzeichen ausrichten . g.0######").alignAtDecimal(numbers.00« (Vorgabe der überladenen alignAtDecimal()-Version).awt. fm = g.drawString("Rendite (%) : " + strings[2].text. } Listing 11: Ausrichtung am Dezimalzeichen bei nichtproportionaler Schrift H inwe is import java.toString(). "#.DecimalFormat. »#.##0.getFontMetrics(). int x = 50. g. Font. x. public void paintComponent(Graphics g) { super.PLAIN.##0.text.##«) Statt die aufbereiteten Stringdarstellungen auf die Konsole auszugeben. FontMetrics fm.0######« und »#.##0. beispielsweise ein JPanel-Feld.drawString("Kapital : " + strings[0].drawString("Bonus : " + strings[1]. g.>> Zahlen und Mathematik 41 Zahlen Abbildung 3: Ausgerichtete Zahlenkolonnen (Formate: »#.toString(). können Sie sie auch in Dateien oder GUI-Komponenten schreiben oder in eine GUI-Komponente. import java.getHeight()). int y = 50. Einzige Bedingung: Es wird eine proportionale Schrift verwendet. x. import java.

stringWidth(strings[i]. "#.getEndIndex())). Position des Dezimalpunkts // feststellen.) Für die Ausrichtung von Zahlen in nichtproportionaler Schrift übernimmt die alignAtDecimal()-Methode zwei weitere Argumente: Zum einen muss sie für jeden formatierten String den Abstand vom Stringanfang bis zum Dezimalzeichen in Pixeln berechnen. ++i) { xOffsets[i] = maxDist .length]. } Listing 11: Ausrichtung am Dezimalzeichen bei nichtproportionaler Schrift (Forts. int[] pixToDecP = new int[numbers. xOffsets). FieldPosition fpos = new FieldPosition(DecimalFormat. Da sie dies nicht allein leisten kann. df. int[] xOffsets) { DecimalFormat df = new DecimalFormat(format). if (maxDist < pixToDecP[i]) maxDist = pixToDecP[i]. int maxDist = 0. } public static StringBuffer[] alignAtDecimal(double[] numbers.format(numbers[i]. das zuvor für den gewünschten Ausgabefont erzeugt wurde (siehe .length. if(numbers. pixToDecP[i] = fm.length != xOffsets. fm.42 Zahlen >> Ausgabe: Zahlenkolonnen am Dezimalzeichen ausrichten * (Version für nicht-proportionale Schrift) */ public static StringBuffer[] alignAtDecimal(double[] numbers. Pixelbreite bis Dezimalpunkt ermitteln for(int i = 0.length]. i < numbers. int[] xOffsets) { return alignAtDecimal(numbers. // nötige Vorarbeiten // Strings erzeugen. strings[i].length. fpos.INTEGER_FIELD).##0.00".substring(0. } return strings. FontMetrics fm.length) throw new IllegalArgumentException("Fehler in Array-Dimensionen"). StringBuffer[] strings = new StringBuffer[numbers. String format. übernimmt sie ein FontMetrics-Objekt. } // xOffsets berechnen for(int i = 0. FontMetrics fm. i < numbers.pixToDecP[i]. fpos). ++i) { strings[i] = new StringBuffer("").

String[] prefix = {"Kapital : ". x + prefixLength + xOffsets[i].setFont(new Font("Times New Roman". fm = g. int prefixLength = 0. 100.stringWidth(prefix[i]).length].length.awt. Listing 12: Fenster mit ausgerichteten Zahlen (aus StartGUI. for (int i = 0.swing. xOffsets). in dem es die Offsetwerte abspeichert.*.java) . int[] xOffsets = new int[numbers. import javax. Zu diesem Zweck übernimmt die Methode ein int-Array. import java. g.getHeight() ).45.toString().drawString(prefix[i]. um den die Ausgabe verschoben werden muss. die am Dezimalzeichen ausgerichtet werden sollen. ++i) if (prefixLength < fm. int x = 50.length. Um dies zu erreichen.*.paintComponent(g).drawString(strings[i]. i < strings. y + i*fm.length]. "#.event. int y = 50. import java.*. Die erste Spalte besteht aus den Strings des Arrays prefix und wird rechtsbündig ausgegeben. Die zweite Spalte enthält die Zahlen des Arrays numbers.PLAIN. StringBuffer[] strings = new StringBuffer[numbers. Neben den formatierten Strings muss die Methode dem Aufrufer für jeden String den xOffset übergeben.1271 }.>> Zahlen und Mathematik 43 Zahlen Listing 12). fm. i < prefix.awt. 24)). for (int i = 0.0######". strings = MoreMath. g. 8. Deren stringWidth()-Methode übergibt sie den Teilstring vom Stringanfang bis zum Dezimalzeichen und erhält als Ergebnis die Breite in Pixel zurück. Die Offsetwerte selbst werden in der zweiten for-Schleife als Differenz zwischen der Pixelposition des am weitesten entfernt liegenden Dezimalzeichens (maxDist) und der Position des Dezimalzeichens im aktuellen String (pixToDecP) berechnet. FontMetrics fm. class MyCanvas extends JPanel { public void paintComponent(Graphics g) { super.getFontMetrics().alignAtDecimal(numbers. ++i) { g. damit die Dezimalzeichen untereinander liegen. die sie im Array pixToDecP speichert."Bonus : ".##0. x."Rendite (%): "}.stringWidth(prefix[i])) prefixLength = fm. Font. public class StartGUI extends JFrame { double[] numbers = { 1230. berechnet das Programm die Länge des größten Strings der 1. Im folgenden Beispiel werden letzten Endes zwei Spalten ausgegeben. Spalte (festgehalten in prefixlength) sowie mit Hilfe von alignAtDecimal() die x-Verschiebungen für die Strings der zweiten Spalte.

oder Mehrzahl (Kongruenz) Kongruenz im sprachwissenschaftlichen Sinne ist die formale Übereinstimmung zusammengehörender Satzglieder. Abbildung 4: Ausrichtung von Zahlenkolonnen bei nichtproportionaler Schrift 11 Ausgabe in Ein.oder Mehrzahl (Kongruenz) y + i*fm. einer Kongruenz im Numerus. Als Programmierer leiden wir vielmehr unter einer ganz anderen Form der Kongruenz. ob der Kongruenz Genüge getan wurde (wie z. ihre wohl bekannteste Form die Übereinstimmung von attributivem Adjektiv und Beziehungswort in Kasus. doch dies ist ein Thema für ein anderes Buch. } } } // Ende von MyCanvas .) Die Strings der ersten Spalte werden einfach mit drawString() an der X-Koordinate x gezeichnet. Numerus und Genus wie in »das kleine Haus« oder »dem kleinen Hund«.B.. die dem Redenden oder Schreibenden eigentlich nie Probleme bereitet. Sie arbeiten mit einem Online-Bestellsystem für eine Bäckerei und wollen dem Kunden zum Abschluss anzeigen. Sie lesen die Anzahl der bestellten Brote aus einer Variablen countBreads und erzeugen folgenden Ausgabestring: "Sie haben " + countBreads + " Brote bestellt. erhält er die Mitteilung: Sie haben 2 Brote bestellt. in denen selbst Leute mit gutem Sprachgefühl nicht gleich sagen können.java) (Forts. Listing 12: Fenster mit ausgerichteten Zahlen (aus StartGUI. Hat er kein oder ein Brot bestellt. liest er auf seinem Bildschirm: Sie haben 0 Brote bestellt.. in »Wie wäre es mit einem Keks oder Törtchen?«). sondern eben nur dem Programmierer: der Kongruenz zwischen Zahlwort und Beziehungswort." Hat der Kunde zwei Brote bestellt. . Es gibt sprachliche Wendungen.44 Zahlen >> Ausgabe in Ein. Angenommen.getHeight() ). Die Strings der zweiten Spalte hingegen werden ausgehend von der Koordinate x zuerst um prefixlength Pixel (um sich nicht mit der ersten Spalte zu überschneiden) und dann noch einmal um xOffsets[i] Positionen (damit die Dezimalzeichen untereinander zu liegen kommen) nach rechts verschoben. wie viele Brote er bestellt hat.

das Argument der format()-Methode in den zurückgelieferten String einzubauen. das ebenso viele Strings enthält.text. "Brot". Die Zahlenbereiche werden als ein Array von double-Werten definiert.\n" und würde für 0. Angegeben wird jeweils der erste Wert im Zahlenbereich. "Brote"} abbilden und in der Ausgabe x. könnten Sie die Grenzen als {0. Die meisten Programmierer lösen dieses Problem. auf {"Brote". 2 und 3 folgende Ausgaben erzeugen: Sie Sie Sie Sie haben haben haben haben 0 1 2 3 Brote bestellt. 1. So definiert das Array double[] limits = {0.5. würde obiger Code leider auch Ausgaben wie »Sie haben 0. "Brot". Brot bestellt.«. "Brote"}. Parameter in Ausgabestrings Leider ist es nicht möglich. ∞) Werte.ChoiceFormat. 0. also beispielsweise: »Sie haben 1 1/2 Brote bestellt. Als Pendant zum Bereichsarray muss ein String-Array definiert werden.5 Brote bestellt.5 als "x 1/2" schreiben.und Mehrzahl angeben – in diesem Fall also Brot(e) – oder die verschiedenen Fälle durch if-Verzweigungen unterscheiden. ChoiceFormat-Instanzen bilden eine Gruppe von Zahlenbereichen auf eine Gruppe von Strings ab.format(countBreads) + " bestellt. Dann bräuchte man nämlich statt countBreads + " " + cf.>> Zahlen und Mathematik 45 Zahlen oder noch schlimmer: Sie haben 1 Brote bestellt. die kleiner als der erste Bereich sind. Darüber hinaus gibt es in Java aber noch eine eigene Klasse. Übergibt man beide Arrays einem ChoiceFormat-Konstruktor. 1} festlegen. 2}.« oder »Sie haben 1. die Zahlenbereiche [0. 1. Um dies zu korrigieren.format(countBreads) H inwe is . outputs). 1) [1. Brote bestellt. Brote bestellt. indem sie das Substantiv in Ein.« erzeugen. die speziell für solche (und noch kompliziertere) Fälle gedacht ist: java. 2) [2. Wäre countBreads eine double-Variable. erzeugt man eine Abbildung der Zahlen aus den angegebenen Bereichen auf die Strings: ChoiceFormat cf = new ChoiceFormat(limits. wie es Bereiche gibt (hier also drei): String[] outputs = {"Brote". werden diesem zugesprochen. Der Ausgabestring für unsere Online-Bäckerei lautete damit: "Sie haben " + countBreads + " " + cf.5 Brot bestellt.

registrieren Sie Letzteres mit Hilfe der setFormatByArgument()-Methode als Formatierer für den Platzhalter. "{0} Brote"}. ChoiceFormat cf = new ChoiceFormat(limits. Will ein Programm dem Anwender die Verarbeitung von Zahlen aus einem bestimmten Zahlensystem erlauben. String[] outputs = {"kein Brot". .text. Für diese Kodierungen ist die Hardware ausgelegt. Dies ist aber genau das. In diesen Text fügen Sie einen Platzhalter für den Zahlenwert ein. Beispielsweise ließe sich dann das unschöne »Sie haben 0 Brote bestellt. Die Lösung bringt in diesem Fall die Klasse java. dass ChoiceFormat den Platzhalter nicht ersetzt. dass Zahlen aus dem betreffenden Zahlensystem eingelesen und ausgegeben werden können. muss es lediglich dafür sorgen. "ein Brot".console().« ersetzen. (Beachten Sie.setFormatByArgumentIndex(0. // zur Verwendung von // System.console() // siehe Rezept 85 Sie erzeugen das gewünschte ChoiceFormat-Objekt und fügen mit {} nummerierte Platzhalter in die Strings ein.« durch »Sie haben kein Brot bestellt.\n").) Dann erzeugen Sie das MessageFormat-Objekt mit dem Ausgabetext. 2}.format(arguments)). ist die hierfür benötigte Funktionalität bereits in den Java-API-Klassen vorhanden. sorgt für die Ersetzung des Platzhalters. das Zahlenwerte Strings zuordnet double[] limits = {0. und was wichtiger wäre: Man könnte in den Ausgabestrings festlegen. das intern unser ChoiceFormat-Objekt verwendet. mf. System.und Gleitkommazahlen. denn das im nächsten Schritt erzeugte MessageFormat-Objekt. cf).46 Zahlen >> Umrechnung zwischen Zahlensystemen nur noch cf. mf. Soweit es die Integer-Zahlen betrifft. // MessageFormat-Objekt erzeugen und mit ChoiceFormat-Objekt verbinden MessageFormat mf = new MessageFormat(" Sie haben {0} bestellt. in diesen Kodierungen finden alle Berechnungen statt.format(countBreads) zu schreiben. sondern unverändert zurückliefert. was wir wollen. outputs). Anschließend müssen Sie nur noch die format()-Methode des MessageFormat-Objekts aufrufen und ihr die zu formatierende Zahl (allerdings in Form eines einelementigen Object-Arrays) übergeben. // Ausgabe Object[] arguments = {new Integer(number)}. 1.MessageFormat: // ChoiceFormat-Objekt erzeugen. ob für einen Bereich der Zahlenwert ausgegeben soll.printf("%s \n". 12 Umrechnung zwischen Zahlensystemen Der Rechner kennt keine Zahlensysteme. Da der Zahlenwert von dem soeben erzeugten ChoiceFormat-Objekt verarbeitet werden soll. er unterscheidet allein zwischen den binären Kodierungen für Integer.

>> Zahlen und Mathematik 47 Zahlen Abbildung 5: Mit ChoiceFormat können Sie (unter anderem) Mengen korrekt in Ein.toString(long n) Andere Systeme Integer. Einlesen Zehnersystem Byte.toString(short n) Integer.und Ausgabe für Zahlen verschiedener Zahlensysteme Mit Hilfe dieser Methoden lässt sich auch leicht ein Hilfsprogramm schreiben.out.toString() Byte.parseInt (String s) Long. int radix) Zehnersystem Byte. 10 und 16) umrechnen kann: public class Start { public static void main(String args[]) { Listing 13: Programm zur Umrechnung zwischen Zahlensystemen .toString(int i.toString(byte n) Short.toString(int n) Long. int radix) Integer.toString() Long. 8.toHexString(int i) System.parseByte (String s) Short.parseLong (String s) Ausgeben Andere Systeme Integer.parseShort (String s) Integer. mit dem man Zahlen zwischen den in der Programmierung am weitesten verbreiteten Zahlensystemen (2.oder Mehrzahl angeben.toString() Integer.printf() Tabelle 7: Ein.toBinaryString(int i) Integer.toOctalString(int i) Integer.parseInt (String s.toString() Short.

" + srcRadix + " \t\t " + args[0]). if (args.out.out.parseInt(args[1]). } number = Integer.println(" System. 10. " + tarRadix + " \t\t " Integer. 8.out. Aufruf mit <Ganzzahl> <Originalbasis> <Zielbasis> .toString(number. System.48 Zahlen >> Umrechnung zwischen Zahlensystemen System.out.) Abbildung 6: Zahlensystemrechner. if ( ! (srcRadix == 2 || srcRadix == 8 || srcRadix == 10 || srcRadix == 16 || tarRadix == 2 || tarRadix == 8 || tarRadix == 10 || tarRadix == 16) ) { System.length != 3) { System. } } } Listing 13: Programm zur Umrechnung zwischen Zahlensystemen (Forts.println(" Ungueltige Basis").exit(0). System.parseInt(args[2]).println().println(" Ungueltiges Argument"). 8.println(" System. System. 10. 16>"). int tarRadix = Integer.println(" Aufruf: Start <Ganzzahl> " + "<Orgin. ----------------------------------").parseInt(args[0]. int srcRadix = Integer. } catch (NumberFormatException e) { System. Basis: 2.out.out. } try { int number = 0. 16> " + "<Zielbasis: 2.println(" + Basis \t Zahl"). srcRadix).err. tarRadix)).println(" System.exit(0).out.

Für das Pattern Matching sind in dem Paket java. Da die Methode getPatternsInString() einen String nach allen Vorkommen des übergebenen Musters durchsuchen soll. die man verarbeiten möchte. beispielsweise einer Datei. Mit boolean find() kann man den String nach übereinstimmenden Vorkommen (»Matches«) durchsuchen. Außerdem ist diese Lösung. obwohl im Einzelfall sicher gangbar und auch sinnvoll. mit dem der erste String durchsucht werden soll. Beispielsweise könnte die Kundennummer eines Unternehmens aus einem Buchstabencode. Gleiches gilt. ruft sie find() in einer while-Schleife auf und speichert die gefundenen Übereinstimmungen in einem ArrayList-Container. wenn der Text so in Tokens zerlegt werden kann. Zur einfacheren Verwendung definieren wir gleich zwei Methoden: ArrayList<String> getPatternsInString(String s. String group() können Sie sich Anfangs. der den String s unter Verwendung des in pat gespeicherten regulären Ausdrucks durchsucht. Der Matcher enthält nun alle benötigten Informationen (das Pattern und den zu durchsuchenden String). Die Methode getPatternsInString() »kompiliert« zuerst den String mit dem regulären Ausdruck p mit Hilfe der statischen Pattern-Methode compile() in ein Pattern-Objekt pat. einem Zahlencode. Dies geht allerdings nur. Eine recht praktische und flexible Lösung für beide oben angeführten Aufgabenstellungen ist dagegen das Extrahieren der Zahlen (oder auch anderer Textpassagen) mittels regulärer Ausdrücke und Pattern Matching.und Endposition bzw.regex die Klassen Pattern und Matcher definiert. ob der gesamte String als ein »Match« für den regulären Ausdruck angesehen werden kann. Der erste Aufruf sucht ab dem Stringanfang. die Methoden zum Finden der übereinstimmenden Vorkommen stellt die Klasse Matcher selbst zur Verfügung: Mit boolean matches() kann man prüfen. nachfolgende Aufrufe setzen die Suche hinter dem letzten gefundenen Vorkommen fort. werden in einer ArrayList<String>Collection zurückgeliefert. Dieses Matcher-Objekt liefert die Pattern-Methode matcher(). dies zu bewerkstelligen. und als zweites Argument den regulären Ausdruck (gegeben als String). der als einziges Argument der zu durchsuchende String übergeben wird. die durch den regulären Ausdruck beschrieben werden. Alle gefundenen Vorkommen von Textpassagen. . dass die Zahlen als Tokens verfügbar sind. extrahieren müssen. Eine Möglichkeit.>> Zahlen und Mathematik 49 Zahlen 13 Zahlen aus Strings extrahieren Manchmal sind die Zahlen. Als Nächstes wird ein Matcher benötigt.util. Mit int start() und int end() bzw. einem Geburtsdatum im Format TTMMJJ und einem abschließenden einbuchstabigen Ländercode bestehen: KDnr-2345-150474a Wenn Sie aus einem solchen String die Zahlen herausziehen möchten. können Ihnen die im Rezept 8 vorgestellten Methoden nicht mehr weiterhelfen. der durchsucht werden soll. wenn Sie Zahlen aus einem größeren Text. in Strings eingebettet. das komplette zuletzt gefundene Vorkommen zurückliefern lassen. wäre das Einlesen des Textes mit einem Scanner-Objekt. per se doch recht unflexibel. String p) ArrayList<String> getNumbersInString(String s) Die Methode getPatternsInString() übernimmt als erstes Argument den String.

die das Komma als Tausenderzeichen verwenden).Matcher. Dieser reguläre Ausdruck findet ganze Zahlen. /** * Pattern in einem Text finden und als ArrayList zurückliefern */ public static ArrayList<String> getPatternsInString(String s. } Listing 14: Strings nach beliebigen Patterns durchsuchen Die zweite Methode. gezielt nach englischen Gleitkommazahlen. import java.regex. aber auch Zahlenschrott (beispielsweise Teile englisch formatierter Zahlen.add( m. "[+-]?\\d+(\\.util.group() ). Zahlen mit und ohne Vorzeichen. der mit Komma eingeleitet wird und mit einer mindestens einelementigen Folge von Ziffern ((\\.util.util. String p) { ArrayList<String> matches = new ArrayList<String>(10).find()) matches. Wenn Sie Zahlenschrott ausschließen. liefert im String gefundene Zahlen zurück. Matcher m = pat. import java. while(m. der Zahlen beschreibt: /** * Zahlen in einem Text finden und als ArrayList zurückliefern */ public static ArrayList<String> getNumbersInString(String s) { return getPatternsInString(s. getNumbersInString().regex. Dank getPatternsInString() fällt die Implementierung von getNumbersInString() nicht mehr sonderlich schwer: Die Methode ruft einfach getPatternsInString() mit einem regulären Ausdruck auf. können Sie nach dem Muster von getNumbersInString() eine eigene Methode definieren oder getPatternsInString() aufrufen und den passenden regulären Ausdruck als Argument übergeben. Pattern pat = Pattern.matcher(s).ArrayList. return matches. Gleitkommazahlen mit Komma zum Abtrennen der Nachkommastellen und ohne Tausenderzeichen.\\d+)?) endet.Pattern.compile(p). } Listing 15: Strings nach Zahlen durchsuchen Der reguläre Ausdruck von getNumbersInString() setzt Zahlen aus drei Teilen zusammen: einem optionalen Vorzeichen ([+-]?). Hexadezimalzahlen oder irgendwelchen sonstigen Textmustern suchen wollen.50 Zahlen >> Zahlen aus Strings extrahieren import java. . einer mindestens einelementigen Folge von Ziffern (\\d+) und optional einem dritten Teil.\\d+)?"). beliebig formatierten Zahlen.

Die Arbeit mit dieser Klasse sieht so aus. 1.println(n). generator. Wenn Sie an double-Zufallszahlen aus dem Bereich 0. Dies liegt daran.0. Ein solcher Algorithmus bildet eine Zahlenfolge.nextInt()..out.nextInt(10). Listing 16: Aus Start.println(n). "\\d+"). numbers = MoreMath.util. H i n we i s . System.0 bis 1. ++i) System.println(n). // // // // // liefert liefert liefert liefert liefert Wert zwischen [0.nextBoolean().println("\n Suche in Kundennummer: \n"). Random generator = new Random().out. double boolean long int d b l i i = = = = = generator. dass man von der Periodizität der erzeugten Zahlenfolge nichts merkt. Die Erzeugung von Zufallszahlen mit Hilfe von Computern ist im Grunde gar nicht zu realisieren. sondern können direkt die Math-Methode random() aufrufen: double d = Math.println("\n Suche nach Zahlen und ähnlichen Passagen: \n").println("\n Suche nach (deutschen) Zahlen: \n").out.getPatternsInString(str. generator.getNumbersInString(str). numbers = MoreMath.0) true oder false zufälligen long-Wert zufälligen int-Wert Wert zwischen [0. brauchen Sie Random nicht selbst zu instanzieren.println(generator. for (String n : numbers) System.util.Random.java 14 Zufallszahlen erzeugen Zur Erzeugung von Zufallszahlen gibt es in der Java-Klassenbibliothek den »Zufallszahlengenerator« java.out. Allerdings sind die Algorithmen.getPatternsInString("KDnr-2345-150474a". generator. import java. for (String n : numbers) System. die man zur Erzeugung von Zufallszahlen in Programmen verwendet. sondern von einem mathematischen Algorithmus errechnet werden.out. i < 5.out. System.random().nextLong().Random.]+\\d+|\\d+"). "[+-]?[\\d. ArrayList<String> numbers = MoreMath. die sich zwangsweise irgendwann wiederholt. generator. dass Sie zuerst ein Objekt der Klasse erzeugen und sich dann durch Aufrufe der entsprechenden next-Methoden der Klasse Random die gewünschten Zufallswerte zurückliefern lassen.out.nextInt(100)).>> Zahlen und Mathematik 51 Zahlen System. for (String n : numbers) System. dass die Zahlen letzten Endes nicht zufällig gezogen werden. 1) // Fünf ganzzahlige Zufallszahlen zwischen 0 und 100 ausgeben for(int i = 0.nextDouble(). so leistungsfähig.0 interessiert sind.

je mehr die Werte vom Erwartungswert abweichen. die Schrauben herstellt. dass sie alle Zahlen aus ihrem Wertebereich mit gleicher Wahrscheinlichkeit zurückliefern. dass die Durchmesser der produzierten Schrauben normalverteilt sind und die Standardabweichung unterhalb eines vorgegebenen Qualitätslimits liegt. Normalverteilung Normalverteilte Größen zeichnen sich dadurch aus. die Standardabweichung ist ein Maß dafür. die Wahrscheinlichkeit für andere Werte nimmt kontinuierlich ab. ergibt sich das Problem. Die zum Testen einer solchen Software benötigten normalverteilten Zufallszahlen liefert die Random-Methode nextGaussian(): Random generator = new Random(). Der Erwartungswert ist am häufigsten vertreten. die mit Zufallszahlen arbeiten. dass die Anwendung bei jedem neuen Start mit anderen Zufallszahlen arbeitet und daher unterschiedliche Ergebnisse produziert. die sicherstellen soll.nextGaussian(). Der Erwartungswert der zurückgelieferten Zufallszahlen ist 0. In Natur und Technik hat man es dagegen häufig mit Größen zu tun. wie schnell die Wahrscheinlichkeit der Werte bei zunehmender Abweichung vom Mittelwert abnimmt. Durch nachträgliche Skalierung und Addition eines Offsets können beliebige normalverteilte Zahlen erzeugt werden. könnte beispielsweise Software für eine Messanlage in Auftrag geben. double d = generator. die normalverteilt sind. Das Aufspüren von Fehlern ist unter solchen Bedingungen .52 Zahlen >> Zufallszahlen erzeugen Gaußverteilte Zufallszahlen Die oben aufgeführten next-Methoden sind so implementiert. dass die möglichen Werte um einen mittleren Erwartungswert streuen. Eine Firma. die Standardabweichung 1. Häufigkeit E x k ur s s s (Standardabweichung) x (Mittelwert) Wert Abbildung 7: Gaußsche Normalverteilung. Zufallszahlen zum Testen von Anwendungen Beim Testen von Anwendungen.

int max).random() eine eigene Methode randomInt(int min. int max) { if (randomNumberGenerator == null) initRNG(). wählt er den Seed unter Berücksichtigung der aktuellen Zeit. public class MoreMath { . H i n we i s 15 Ganzzahlige Zufallszahlen aus einem bestimmten Bereich Mit Hilfe der Methode Random. dass sie auch reproduzierbare Folgen von Zufallszahlen erzeugen können.util. wenn Sie Zufallszahlen als Eingaben zum Testen der Anwendung benutzen.... Wenn Sie Integer-Zahlen aus einem beliebigen Bereich benötigen.nextInt(int n) können Sie sich eine zufällige Integer-Zahl aus dem Bereich von 0 bis n (exklusive) zurückliefern lassen. der Sie nur noch die gewünschten Bereichsgrenzen übergeben müssen: import java. müssen Sie entweder die Anzahl Zahlen im Bereich selbst berechnen. . erzeugt der zugehörige Zufallsgenerator bei jedem Start der Anwendung die gleiche Folge von Zufallszahlen und Sie können die Anwendung mit reproduzierbaren Ergebnissen testen. // nur einen Zufallsgenerator verwenden private static synchronized void initRNG() { if (randomNumberGenerator == null) randomNumberGenerator = new Random(). Zur Einstellung dient der so genannte Seed. Wenn Sie den Random-Konstruktor ohne Argument aufrufen (siehe vorangehende Abschnitte). (Gleiches gilt. als Argument an nextInt() übergeben und zu der zurückgelieferten Zufallszahl die erste Zahl im gewünschten Bereich hinzuaddieren .. So wird gewährleistet..>> Zahlen und Mathematik 53 Zahlen natürlich sehr schwierig. // private Instanz des verwendeten Zufallsgenerators private static Random randomNumberGenerator. } /** * Zufallszahl aus vorgegebenem Bereich zurückliefern */ public static int randomInt(int min..) Aus diesem Grunde lassen sich Zufallsgeneratoren in der Regel so einstellen. oder Sie definieren sich nach dem Muster von Math. Jeder Seed erzeugt genau eine vordefinierte Folge von Zufallszahlen. dass bei jedem Aufruf ein individueller Seed und damit eine individuelle Zahlenfolge erzeugt wird. Wenn Sie also dem Random-Konstruktor einen festen Seed-Wert vorgeben: Random generator = new Random(3). Listing 17: Ganzzahlige Zufallszahlen aus einem definierten Bereich .Random.

Die Methode muss sicherstellen. nicht gleiche Zufallszahlen erzeugen (Lottozahlen) int number = randomNumberGenerator.) Zwei Dinge sind zu beachten: Die Methode liefert eine Zahl aus dem Bereich [min.nextInt( max+1-min ). umso größer die Wahrscheinlichkeit.Random. nextInt(n). Eine besonders elegante Möglichkeit dafür dies zu tun. ist die Obergrenze also in den Wertebereich mit eingeschlossen.54 Zahlen >> Mehrere. Zu diesem Zweck wurde für den Generator ein privates statisches Feld definiert. import java. bietet die Collection-Klasse TreeSet. Sofern Sie also an einmaligen Zufallszahlen. 16 Mehrere. das eine Zahl aus [0. ob der Generator erzeugt werden muss oder schon vorhanden ist. Die folgende Methode erzeugt einen TreeSet-Container für size Integer-Zahlen und füllt diesen mit Werten aus dem Bereich [min. der bei nachfolgenden Aufrufen verwendet wird. doch dies erlaubt es. dass dies passiert.util.TreeSet. wie sie beispielsweise zur Simulation einer Lottoziehung benötigt werden. Je kleiner der Wertebereich. int min. int max) { TreeSet<Integer> numbers = new TreeSet<Integer>(). } } Listing 17: Ganzzahlige Zufallszahlen aus einem definierten Bereich (Forts. wenn diese noch nicht im TreeSet-Container enthalten sind. dass sie nur beim ersten Aufruf einen Random-Zufallsgenerator erzeugt. /** * Erzeugt size nicht gleiche Zufallszahlen aus Wertebereich von * min bis max */ public static TreeSet<Integer> uniqueRandoms(int size. max] zurück. import java.util. interessiert sind. im Gegensatz zu Random. return min + number. n) zurückliefert. müssen Sie die Dubletten herausfiltern. die Methode als synchronized zu deklarieren und die Erzeugung des Zufallszahlengenerators so threadsicher zu machen. Ein wenig umständlich erscheint die Auslagerung der if-Abfrage in eine eigene private Methode. kann es schnell passieren. n) zurückliefern lassen. Deren add()Methode fügt neue Elemente nämlich nur dann ein. Eine einfache if-Bedingung prüft. dass Sie die eine oder andere Zahl mehrfach erhalten. max]. Random generator = new Random(). int n. nicht gleiche Zufallszahlen erzeugen (Lottozahlen) Wenn Sie sich von der Methode nextInt(int n) Zufallszahlen aus dem Wertebereich [0. Listing 18: Methode zur Erzeugung einmaliger Zufallszahlen .

) Wenn es in dem spezifizierten Wertebereich nicht genügend Zahlen gibt. da die Bedingung der while-Schleife bereits sicherstellt.min). import java.) Ist der Wertebereich größer als die gewünschte Anzahl Zufallszahlen. wie Zahlen in den Container eingefügt werden sollen.util. System. } else { while(numbers. den Rückgabewert der add()-Methode zu überprüfen (true oder false). TreeSet<Integer> randomNumbers = MoreMath. wie mit Hilfe von uniqueRandoms() die Ziehung der Lottozahlen (6 aus 49) simuliert werden kann. i <= max. werden die Zahlen mit Hilfe einer for-Schleife in den Container eingefügt.println().uniqueRandoms(6.add(n).>> Zahlen und Mathematik 55 Zahlen if (size > max+1-min) throw new IllegalArgumentException("Gibt nicht genügend " + "eindeutige Zahlen im Bereich!"). wird eine IllegalArgumentException ausgeworfen. public class Start { public static void main(String args[]) { System. (In diesem Fall kann eigentlich nicht mehr von Zufallszahlen die Rede sein.size() != size) { n = min + generator.TreeSet. um den Container ohne Dubletten zu füllen. Beachten Sie. werden die Zahlen zufällig gezogen. // Zahl einfügen. 49). bis der Container die gewünschte Anzahl Elemente enthält. dass die Ziehung nicht vorzeitig beendet wird.out.out. if (size == max+1-min) { for(int i= min.add(i). Das folgende Programm zeigt. } Listing 18: Methode zur Erzeugung einmaliger Zufallszahlen (Forts. Listing 19: Lottozahlen . } } return numbers.nextInt(max+1 . 1. ++i) numbers. falls nicht schon vorhanden numbers. dass wir uns hier nicht die Mühe machen. Wenn der spezifizierte Wertebereich gerade genau so viele Zahlen enthält.println("Willkommen zur Ziehung der Lottozahlen!").

0 ergibt.56 Zahlen >> Trigonometrische Funktionen for(int elem : randomNumbers) System. 180 Grad entsprechen 1 π. Math stellt zur bequemen Umrechnung von Grad in Radiant und umgekehrt die Methoden toDegrees() und toRadians() zur Verfügung.0)) exakt 0.) 18 Temperaturwerte umrechnen (Celsius <-> Fahrenheit) Während die Wissenschaft und die meisten Völker dieser Welt die Temperatur mittlerweile in Grad Celsius messen.32) * 5. dass häufig getippte Zahlenkombinationen (siehe Fachliteratur zu Spielsystemen) aussortiert werden. dass diese Umrechnung nicht immer exakt ist.0/9. Wenn Sie ausrechnen wollen. multiplizieren Sie einfach die Winkelangabe mit 2 * π und teilen Sie das Ganze durch 360 (oder multiplizieren Sie mit π und teilen Sie durch 180). Viel mehr dürfte hinter den Spielsystemen kommerzieller Anbieter auch nicht stecken. sondern als Länge des Bogens angegeben. bauen Sie das Programm und die Methode uniqueRandoms() doch so aus. 90 Grad entsprechen 1/2 π. 360 Grad entsprechen also genau 2 π.) Wenn Sie selbst Lotto spielen. 17 Tipp Trigonometrische Funktionen Bei Verwendung der trigonometrischen Methoden ist zu beachten. dass diese Methoden als Parameter stets Werte in Bogenmaß (Radiant) erwarten. (Siehe auch Rezept 6 zum Vergleichen mit definierter Genauigkeit. Beim Bogenmaß wird der Winkel nicht in Grad. den der Winkel aus dem Einheitskreis (Gesamtumfang 2 π) ausschneidet: 1 rad = 360º/2 π.PI/180 * grad. dass sin(toRadians(180.println(" " + elem). } } Listing 19: Lottozahlen (Forts. } /** * Umrechnung von Celsius in Fahrenheit */ .0. Beachten Sie aber. Gehen Sie also beispielsweise nicht davon aus. 1º = 2 π/360 rad.out. Die Formel zur Umrechnung von Fahrenheit in Celsius lautet: c = ( f − 32) * 5 / 9 Aus dieser Formel lassen sich schnell zwei praktische Methoden zur Umrechnung von Fahrenheit in Celsius und umgekehrt ableiten: /** * Umrechnung von Fahrenheit in Celsius */ public static double fahrenheit2Celsius(double temp) { return (temp . was 32 Grad in Radiant sind. bogenlaenge = Math. ist in den USA immer noch die Einheit Fahrenheit gebräuchlich.

. gibt es 7! Möglichkeiten (Permutationen). Fakultät berechnen wenn n = 0 für n = 1. fac(n) = n * fac(n-1).0 – wohl aber der Compiler.. den Nachkommaanteil unterschlägt. Die Fakultät ist vor allem für die Berechnung von Wahrscheinlichkeiten wichtig. /** * Fakultät berechnen Listing 20: Methode zur Berechnung der Fakultät .>> Zahlen und Mathematik public static double celsius2fahrenheit(double temp) { return (temp * 9 / 5. Abbildung 8: Programm zur Umrechnung zwischen Fahrenheit und Celsius 19 A cht un g Die Mathematik unterscheidet nicht zwischen 5/9 und 5. Mathematisch ist die Fakultät definiert als: n! = 1. d. der im ersten Fall eine Ganzzahlendivision durchführt. in einen Behälter geben und dann nacheinander ziehen. Wenn Sie beispielsweise sieben Kugeln. . die Kugeln zu ziehen.java zu diesem Rezept können Sie beliebige Temperaturwerte umrechnen.. n! = 1 * 2 * 3 .0) + 32. oder rekursiv formuliert: fac(0) = 1. * (n-1) * n.0/9. Geben Sie einfach in der Kommandozeile die Ausgangseinheit (-f für Fahrenheit oder -c für Celsius) und den umzurechnenden Temperaturwert an.h. nummeriert von 1 bis 7. } 57 Zahlen Mit dem Programm Start.

--n.58 Zahlen >> Fakultät berechnen */ public static double factorial(int n) { double fac = 1. Übergeben Sie n beim Aufruf in der Konsole und denken Sie daran. if (n < 2) return fac. Bereits für relativ kleine Eingaben wie die Zahl 10 ergibt sich ein sehr hoher Wert (10! = 3. } return fac. dass Sie ab 171 nur noch infinity-Ausgaben ernten. Die Fakultät ist eine extrem schnell ansteigende Funktion.) Mit dem Start-Programm zu diesem Rezept können Sie sich die Fakultäten von 0 bis n ausgeben lassen. Abbildung 9: Fakultät A c h tun g. if (n < 0) throw new IllegalArgumentException("Fakultaet ist nur fuer " + "positive Zahlen definiert").628.800) und 171! liegt schon außerhalb des Wertebereichs von double! . while(n > 1) { fac *= n. } Listing 20: Methode zur Berechnung der Fakultät (Forts.

.length).pow(sum.. ⋅ xn x= n 1 1 1 + + . + xn n geometrischer harmonischer x = n x1 ⋅ x2 ⋅ . + x1 x2 xn quadratischer x= Tabelle 8: Mittelwerte 1 2 2 2 x1 + x2 + . return Math... 1... } . Mittelwert arithmetischer Berechnung x= x1 + x2 + . } /** * Geometrisches Mittel */ public static double geomMean(double. Als Argument kann den Methoden daher ein doubleArray oder eine beliebig lange Folge von double-Werten übergeben werden.. /** * Arithmetisches Mittel (Standard für Mittelwertberechnungen) */ public static double arithMean(double... + xn n ( ) Die folgenden Methoden zur Berechnung der verschiedenen Mittelwerte wurden durchweg mit einem double.>> Zahlen und Mathematik 59 Zahlen 20 Mittelwert berechnen Wenn wir den Mittelwert oder Durchschnitt einer Folge von Zahlen berechnen.length.. values) { double sum = 0. for (double d : values) sum *= d.-Parameter definiert.. bilden wir üblicherweise die Summe der einzelnen Werte und dividieren diese durch die Anzahl der Werte. for (double d : values) sum += d.. In der Mathematik bezeichnet man dies als das arithmetische Mittel und stellt es weiteren Mittelwerten gegenüber.. values) { double sum = 1. return sum/values..0/values.

} /** * Quadratisches Mittel */ public static double squareMean(double.. Kommen monatliche Raten dazu. return values.5. 3}.5..length). 12.geomMean(1. values) { double sum = 0.arithMean(values). return Math.0/d. oder MoreMath.sqrt(sum/values. Kn ist das Endkapital. erweitert sich die Formel zu: K n = K 0 ⋅ (1 + i ) + R ⋅ n (1 + i )n − 1 (1 + i )1/12 − 1 . MoreMath. das man erhält. Zinseszins berechnen n Die Grundformel zur Zinseszinsrechnung lautet: K n = K 0 ⋅ (1 + i ) wobei n die Laufzeit in Jahren und i den Jahreszinssatz (p/100) bezeichnet.. 3) 21 Achtung Die Methode geomMean() liefert NaN zurück. wenn man das Startkapital K0 für n Jahre (oder allgemein Zinsperioden) zu einem Zinssatz i verzinsen lässt. 0. 5.5. 5.5.60 Zahlen >> Zinseszins berechnen /** * Harmonisches Mittel */ public static double harmonMean(double. wenn die Summe der Werte negativ ist (wegen Ziehen der n-ten Wurzel). values) { double sum = 0. for (double d : values) sum += 1.length / sum. for (double d : values) sum += d*d. 12. } Mögliche Aufrufe wären: double[] values = {1.. 0.

} Die Höhe der reinen Einzahlungen berechnet paidInCapital(): /** * Berechnung des eingezahlten Kapitals */ public static double paidInCapital(double startCapital. 1/12.pow(accumulationFactor . Nach Drücken des BERECHNEN-Schalters wird die jährliche Kapitalentwicklung berechnet und in der JTextArea-Komponente links angezeigt.1) * Math.0). double interest. Verzinsung in Prozent und Laufzeit in Jahren werden über JTextField-Komponenten abgefragt. die Höhe der Raten (installment). double installment. term) . Startkapital. das 0 sein kann. double endCapital = startCapital * Math. } Das Start-Programm zu diesem Rezept nutzt obige Methoden zur Implementierung eines Zinsrechners. double interestRate = interest/100. und die Laufzeit (term). double installment. int term) { double endCapital = startCapital. . den Zins in Prozent (interest). Als Argumente übernimmt die Methode das Startkapital. ++n) endCapital = endCapital + 12*installment. int term) { if(interest == 0.0) .1) / (Math.pow(accumulationFactor . /** * Kapitalentwicklung bei monatlicher Ratenzahlung und Zinseszins */ public static double capitalWithCompoundInterest(double startCapital. return endCapital. for (int n = 1.1/12.0. term) + installment * (Math. double accumulationFactor = 1 + interestRate. der nicht 0 sein darf.pow(accumulationFactor . return endCapital . monatliche Raten.>> Zahlen und Mathematik 61 Zahlen In der Finanzwelt wird aber meist mit der folgenden Variante für vorschüssige Renten gerechnet: K n = K 0 ⋅ (1 + i ) + R ⋅ n (1 + i )n − 1 ⋅ (1 + i )1/ 12 (1 + i )1 /12 − 1 Die Methode capitalWithCompoundInterest() berechnet nach obiger Formel das Endkapital nach n Jahren monatlicher Ratenzahlung und Zinseszinsverzinsung. n <= term.0) throw new IllegalArgumentException("Zins darf nicht Null sein").pow(accumulationFactor .

Vereinfacht werden Zahlen oft als Paare aus Real. so zum Beispiel bei der Berechnung von Fraktalen.und Imaginärteil können komplexe Zahlen daher auch als Kombination aus Radius und Winkel zwischen der Verbindungslinie zum Koordinatenursprung und der positiven reellen x-Achse angegeben werden (Polarkoordinaten). bilden aber ein wichtiges Teilgebiet der Algebra und finden als solches immer wieder Eingang in die Programmierung. Statt als Paar aus Real. siehe Abbildung 11). .und Imaginärteil geschrieben: (x. Der Radius wird dabei üblicherweise als Betrag. y als Imaginärteil und i als die imaginäre Einheit bezeichnet wird (mit i2 = -1). Komplexe Zahlen haben die Form z = x + iy wobei x als Realteil.62 Zahlen >> Komplexe Zahlen Abbildung 10: Zinsrechner 22 Komplexe Zahlen Komplexe Zahlen gehören zwar nicht unbedingt zum täglichen Handwerkszeug eines Programmierers. der Winkel als Argument bezeichnet. y). imaginäre Achse 4 + 2i i 1 reelle Achse Abbildung 11: Komplexe Zahlen in Koordinatendarstellung Grafisch werden komplexe Zahlen in einem Koordinatensystem dargestellt (Gaußsche Zahlenebene.

. y – y') Die Subtraktion entspricht der Addition der Negativen (-z = -x -yi) Vervielfachung ist die Multiplikation mit einer reellen Zahl. Die Methoden für die Grundrechenarten sind durch statische Versionen überladen. 3 * (x. sowie Get-Methoden. der eine komplexe Zahl aus Radius (Betrag) und Winkel (Argument) berechnet. y) – (x'. y) + (x'. y) * (x'. xy' + yx') Die Division z/z' ist gleich der Multiplikation mit der Inversen z*z-1. (x. die Radius und Winkel eines gegebenen Complex-Objekts zurückliefern. indem man die Realteile und Imaginärteile addiert. Vervielfachung und Multiplikation. Negativer. y + y') Komplexe Zahlen werden subtrahiert. Konjugierter und Inverser sowie Methoden für die Grundrechenarten Addition. Die nichtstatischen Methoden verändern das aktuelle Objekt. y) = (3*x. y') = (x + x'. y') = (xx'-yy'. y') = (x – x'.>> Zahlen und Mathematik 63 Zahlen Rechnen mit komplexen Zahlen Operation Betrag Beschreibung Der Betrag einer komplexen Zahl ist die Quadratwurzel aus der Summe der Komponentenquadrate. Subtraktion. 3*y) Die Multiplikation zweier komplexer Zahlen ist gegeben durch: (x. (x. die statischen Methoden liefern das Ergebnis der Operation als neues Complex-Objekt zurück. z = Addition x2 + y2 Komplexe Zahlen werden addiert. indem man die Realteile und Imaginärteile voneinander subtrahiert. Die Division kann durch Multiplikation mit der Inversen berechnet werden. Die Inverse einer komplexen Zahl ist definiert als: x y z −1 = 2 i − 2 2 x +y x + y2 Subtraktion Vervielfachung Multiplikation Division Tabelle 9: Rechenoperationen für komplexe Zahlen Die Klasse Complex Die Klasse Complex definiert neben verschiedenen Konstruktoren Methoden für die Berechnung von Betrag. Zur Unterstützung der Polarkoordinatendarstellung gibt es einen Konstruktor.

Um diesen Konstruktor von dem zweiten Konstruktor unterscheiden zu können. Weist dem Realteil der aktuellen komplexen Zahl einen Wert zu. deren Real. Weist dem Imaginärteil der aktuellen komplexen Zahl einen Wert zu. Die statische Version addiert die beiden übergebenen komplexen Zahlen und liefert das Ergebnis zurück. Liefert den Imaginärteil der aktuellen komplexen Zahl zurück. Die statische Version addiert die reelle Zahl s zur übergebenen komplexen Zahl a und liefert das Ergebnis zurück.POLAR übergeben. double getReal() void setReal(double real) double getImag() void setImag(double real) double getR () double getPhi () void add(Complex a) public static Complex add(Complex a. Liefert den Realteil der aktuellen komplexen Zahl zurück.und Imaginärteil. dem Sie einfach die Konstante Complex. Die statische Version subtrahiert die reelle Zahl s von der übergebenen komplexen Zahl a und liefert das Ergebnis zurück.und Imaginärteil 0. double phi. Der zweite Konstruktor erzeugt eine komplexe Zahl mit den übergebenen Werten für Real. Der Standardkonstruktor erzeugt eine komplexe Zahl. Der dritte Konstruktor rechnet die übergebenen Werte für Radius und Winkel in Real. byte polar) Beschreibung Konstruktoren.0 ist. (Polarkoodinatendarstellung) Liefert den Winkel (Argument) der aktuellen komplexen Zahl zurück. double s) void subtract(Complex a) public static Complex subtract(Complex a. double imag) Complex(double r. Complex b) void subtract(double s) public static Complex subtract(Complex a. Subtrahiert die übergebene reelle Zahl von der aktuellen komplexen Zahl. Complex b) void add(double s) public static Complex add(Complex a. (Polarkoodinatendarstellung) Addiert die übergebene komplexe Zahl zur aktuellen komplexen Zahl. Die statische Version subtrahiert die zweite übergebene komplexe Zahl von der ersten und liefert das Ergebnis zurück.64 Zahlen >> Komplexe Zahlen Methode Complex() Complex(double real. Subtrahiert die übergebene komplexe Zahl von der aktuellen komplexen Zahl. Addiert die übergebene reelle Zahl zur aktuellen komplexen Zahl.und Imaginärteil um und erzeugt das zugehörige Complex-Objekt. Liefert den Radius (Betrag) der aktuellen komplexen Zahl zurück. double s) Tabelle 10: Methoden der Klasse Complex . ist ein drittes Argument notwendig.

Liefert eine String-Darstellung der komplexen Zahl zurück: x + yi void multiply(Complex a) public static Complex multiply(Complex a. -yi). Complex b. double eps) int hashCode() String toString() Tabelle 10: Methoden der Klasse Complex (Forts.>> Zahlen und Mathematik 65 Zahlen Methode void times(double s) public static Complex times(Complex a. /*** Konstruktoren ***/ Listing 21: Complex. private double real = 0. private double imag = 0. Die statische Version multipliziert die übergebene komplexe Zahl a mit der reellen Zahl s und liefert das Ergebnis zurück.java // Realteil // Imaginärteil . Die statische Version multipliziert die beiden übergebenen komplexen Zahlen und liefert das Ergebnis zurück. Complex b) void negate() double abs() Complex conjugate() Complex inverse() Object clone() boolean equals(Object obj) static boolean equals(Complex a.0. Liefert den Betrag der komplexen Zahl zurück. Imaginärteile der beiden komplexen Zahlen werden dann als »gleich« angesehen. Liefert die konjugiert komplexe Zahl (x. Die statische Version erlaubt für den Vergleich die Angabe einer Genauigkeit eps. wenn das übergebene Objekt vom Typ Complex ist und Real. Erzeugt eine Kopie der aktuellen komplexen Zahl. Negiert die aktuelle komplexe Zahl (-x. Die Real. double s) Beschreibung Multipliziert die aktuelle komplexe Zahl mit der übergebenen reellen Zahl s. Liefert die Inverse zur aktuellen komplexen Zahl zurück. Multipliziert die aktuelle komplexe Zahl mit der übergebenen komplexen Zahl. Zur Überschreibung der equals()-Methode siehe auch Rezept 252.und Imaginärteil die gleichen Werte wie die aktuelle komplexe Zahl besitzen.0.bzw. Liefert einen Hashcode für die aktuelle komplexe Zahl zurück. Zur Überschreibung der clone()-Methode siehe auch Rezept 250.) /** * Klasse für komplexe Zahlen */ public class Complex implements Cloneable { public final static byte POLAR = 1. wenn ihre Differenz kleiner eps ist. Liefert true zurück. -y) zur aktuellen komplexen Zahl zurück.

real = a. } public double getImag() { return imag.real.0. this. this. } public Complex(double real.atan2(this. } // Addition c = a + b public static Complex add(Complex a. double imag) { this.cos(phi).java (Forts.sin(phi).imag = r * Math.real = 0.imag.imag = imag. this. byte polar) { this. } /*** Rechenoperationen ***/ // Addition this += a public void add(Complex a ) { this.imag. } /*** Get.imag + b. } public Complex(double r. this. Complex b) { Complex c = new Complex().imag.real = real. double phi.real). } public double getPhi() { return Math. } public void setImag(double imag) { this.imag = imag.abs(). c.) .imag = a.real.real + b.0. this. c.66 Zahlen >> Komplexe Zahlen public Complex() { this.real += a.und Set-Methoden ***/ public double getReal() { return real.real = real.imag = 0.imag += a. } public void setReal(double real) { this. Listing 21: Complex. } public double getR() { return this.real = r * Math.

real = a. c. r = a. c. return c.real *= s. c.java (Forts. } // Subtraktion einer Gleitkommazahl public void subtract(double s) { this.imag = a. this. Complex b) { Complex c = new Complex().imag * s. } // Addition einer Gleitkommazahl public void add(double s) { this.real * s. this.>> Zahlen und Mathematik 67 Zahlen return c.real -= a.real. c. return c.real += s. return c. } // Subtraktion c = a .b. i).imag -= a.imag = a. i. } // Subtraktion einer Gleitkommazahl public static Complex subtract(Complex a. double s){ double r.real -= s.imag = a. return new Complex(r.imag.real .real + s.imag.real . } // Vervielfachung durch Multiplikation mit Gleitkommazahl public void times(double s) { this.s.imag *= s. } // Addition einer Gleitkommazahl public static Complex add(Complex a.real = a.) . } // Subtraktion this -= a public void subtract(Complex a ) { this. double s) { Complex c = new Complex(). c.imag.imag. } public static Complex times(Complex a.imag . double s) { Complex c = new Complex().b.real = a. Listing 21: Complex.b public static Complex subtract(Complex a.real. c. i = a.

real) . } // Betrag public double abs() { return Math. } // Konjugierte public Complex conjugate() { return new Complex(this.imag)). r = (this.imag) + (a. this.real * b. Complex b){ double r.sqrt(real*real + imag*imag).imag * b.68 Zahlen >> Komplexe Zahlen } // Multiplikation this *= b public void multiply(Complex b){ double r. } // Multiplikation c = a * b public static Complex multiply(Complex a.imag * b. return new Complex(r.real). } // Inverse public Complex inverse() { double r.imag) + (this.real * b. i). this.imag / ((this.real.real * this. i).imag * b.real).imag)). this. r = (a.imag).imag = i.(this. return new Complex(r.java (Forts. Listing 21: Complex.real) + (this.real = r. } /*** Sonstige Operationen ***/ // Negation public void negate() { this.imag * this.imag * this.imag). r = this.imag * b.real * b. i. i.real *= -1. i = -this. -this.imag *= -1. i = (a.) .real) .real / ((this.real) + (this.real * b. i. i = (this.(a.real * this.imag).

else return false.imag = this. c.real .java (Forts.real) < eps) &&(Math.clone().imag == tmp.real) || Double. return (((int) bits) ^ ((int) (bits >> 32))).isNaN(this.isNaN(tmp.b.real == tmp. return c.real = this.abs(a.>> Zahlen und Mathematik 69 Zahlen } /*** Überschriebene Object-Methoden ***/ public Object clone() { try { Complex c = (Complex) super. Complex b. } public String toString() { Listing 21: Complex.abs(a. } public static boolean equals(Complex a. } } public boolean equals(Object obj) { if (obj instanceof Complex) { Complex tmp = (Complex) obj.doubleToLongBits(this. else return false. } return false.imag.imag .real) && (this.real.doubleToLongBits(this.imag)) ) return true.real).imag) ) return true.) . double eps) { if (a. c.real) || Double.equals(b)) return true.b.imag) < eps) ) return true.isNaN(tmp.isNaN(this.imag) * 31. } catch (CloneNotSupportedException e) { // sollte nicht vorkommen throw new InternalError(). bits ^= Double. if ( (this. } } public int hashCode() { long bits = Double.imag)) && (Double. dann als gleich ansehen if ( (Double. // wenn beide NaN. else { if( (Math.

) Das Programm aus Listing 22 demonstriert den Einsatz der Klasse Complex anhand der Berechnung einer Julia-Menge. siehe Kategorie »Threads«.*.awt. for(int i = 0.abs() < 1. import javax.##0. } } Listing 21: Complex.imag < 0.012. x. 0. j. ++n) { if (x.0) { g.imag)). 255.awt. public class Start extends JFrame { class MyCanvas extends JPanel { public void paintComponent(Graphics g) { super.0001*j). 255)). ++j) { Complex x = new Complex(0. g.abs(this.##"). if(this.abs()%250. Listing 22: Fraktalberechnung mit Hilfe komplexer Zahlen .format(this. Complex c = new Complex(-0.DecimalFormat df = new java.event.abs() > 100. x.setColor(new Color(0. was uns hier aber nicht weiter stören soll.real). n < 100. import java.) import java.add(c).text.multiply(x). String str_rt = df. String str_it = df. in Ereignisbehandlungscode zeitaufwendige Berechnungen durchzuführen.70 Zahlen >> Komplexe Zahlen String sign = " + ". 255)).setColor(new Color((int)x.format(Math.text.0) sign = " . 0.fillRect(i.java (Forts.swing. } if (x. j < getHeight().0001*i. 1). return str_rt + sign + str_it + "i".*.0) break. java. i < getWidth(). ++i) for(int j = 0.paintComponent(g). (Korrekt wäre die Auslagerung der Berechnung in einen eigenen Thread. Die Berechnung der Julia-Menge erfolgt der Einfachheit halber direkt in paintComponent(). } else { g. Die Folge ist.74). for(int n = 0.DecimalFormat("#. 1. auch wenn dies gegen den Grundsatz verstößt. dass die Benutzerschnittstelle für die Dauer der Julia-Mengen-Berechnung lahm gelegt wird. 0.*.".

>> Zahlen und Mathematik 71 Zahlen g. } public static void main(String args[]) { // Fenster erzeugen und anzeigen Start mw = new Start(). setDefaultCloseOperation(JFrame.350).setSize(500. 1). 1.300). mw.fillRect(i. BorderLayout.setVisible(true). mw. } } } } public Start() { setTitle("Julia-Menge"). getContentPane().add(new MyCanvas().setLocation(200. mw. mw.setResizable(false). j.) Abbildung 12: Julia-Menge .EXIT_ON_CLOSE).CENTER). } } Listing 22: Fraktalberechnung mit Hilfe komplexer Zahlen (Forts.

indem man ihre einzelnen Komponenten addiert. der vom einen Punkt zum anderen führt.72 Zahlen >> Vektoren 23 Vektoren Vektoren finden in der Programmierung vielfache Anwendung – beispielsweise zur Repräsentation von Koordinaten. der von P nach Q führt. ⎛1⎞ ⎛ 5 ⎞ ⎛ 1 − 5 ⎞ ⎛ − 4 ⎞ ⎜ ⎟−⎜ ⎟ = ⎜ ⎟ ⎜ ⎟ ⎜ 3⎟ ⎜ 0 ⎟ ⎜ 3 − 0 ⎟ = ⎜ 3 ⎟ ⎠ ⎝ ⎠ ⎝ ⎠ ⎝ ⎠ ⎝ Für zwei Ortsvektoren p und q erhält man den Vektor.h. ⎛ 10 ⎞ ⎛ 2 *10 ⎞ ⎛ 20 ⎞ 2⎜ ⎟ = ⎜ ⎜ − 7 ⎟ ⎜ 2 * −7 ⎟ = ⎜ − 14 ⎟ ⎟ ⎜ ⎟ ⎝ ⎠ ⎝ ⎠ ⎝ ⎠ Durch die Vervielfachung wird lediglich die Länge. Einer »Division« entspricht die Multiplikation mit einem Faktor zwischen 0 und 1. p = ⎜ 12 ⎟ p=⎜ ⎟ 12 ⎠ ⎝ ⎜ − 1⎟ ⎝ ⎠ Der Abstand zwischen zwei Punkten P und Q ist dann gleich der Länge des Vektors. indem man p von q subtrahiert. (Für zweidimensionale Vektoren lässt sich dies leicht aus dem Satz des Pythagoras ableiten. ⎛1⎞ ⎛ 5⎞ ⎛1 + 5 ⎞ ⎛ 6⎞ ⎜ ⎟+⎜ ⎟=⎜ ⎟ ⎜ ⎟ ⎜ 3⎟ ⎜ 0 ⎟ ⎜ 3 + 0 ⎟ = ⎜ 3 ⎟ ⎠ ⎝ ⎠ ⎝ ⎠ ⎝ ⎠ ⎝ Das Ergebnis ist ein Vektor. Tabelle 11: Vektoroperationen . mit Beginn im Ursprung des Koordinatensystems) repräsentiert werden. ⎛5⎞ ⎜ ⎟ ⎛5⎞ ⎜ ⎟. für Berechnungen mit gerichteten. Subtraktion Vektoren werden subtrahiert. nicht die Richtung des Vektors verändert. für v = (1. physikalischen Größen wie Geschwindigkeit oder Beschleunigung und natürlich im Bereich der dreidimensionalen Computergrafik. -1) als zwei. bzw. 12.bzw. indem man ihre einzelnen Komponenten subtrahiert. dreidimensionale Ortsvektoren (d. der vom Anfang des ersten Vektors zum Ende des zweiten Vektors weist. Vervielfachung Vervielfachung ist die Multiplikation mit einem skalaren Faktor.) v = (1*1) + (3 * 3) . PQ = q − p Rechnen mit Vektoren Operation Länge Beschreibung Die Länge (oder der Betrag) eines Vektors ist die Quadratwurzel aus der Summe der Komponentenquadrate. So können – um ein einfaches Beispiel zu geben – Punkte in der Ebene P(5. 12) oder im Raum (5. 3) Addition Vektoren werden addiert.

und dreidimensionale Vektoren kann es als die Summe der Komponentenprodukte berechnet werden: ⎛1⎞ ⎛ 5 ⎞ ⎜ ⎟ ⋅ ⎜ ⎟ = 1* 5 + 3 * 0 = 5 ⎜ 3⎟ ⎜ 0 ⎟ ⎝ ⎠ ⎝ ⎠ Das Skalarprodukt ist ein skalarer Wert. die nicht übermäßig zeitkritisch sind. der senkrecht zu v und w steht. Der Betrag des Vektorprodukts ist gleich dem Flächeninhalt des von v und w aufgespannten Parallelogramms. (Achtung! Das Vektorprodukt liefert stets einen Vektor. der zur Ebene der Ausgangsvektoren senkrecht steht. Für die Grundrechenarten (Addition. In der 3D-Grafikprogrammierung kann das Vektorprodukt zur Berechnung von Oberflächennormalen verwendet werden. Vektorprodukt Das Vektorprodukt (englisch »cross product«) ist das Produkt aus den Längen (Beträgen) zweier Vektoren multipliziert mit dem Sinus des Winkels zwischen den Vektoren. v ⋅ w = v w cos α Für zwei. v × w = v w sin α Für dreidimensionale Vektoren kann es wie folgt aus den Komponenten berechnet werden: ⎛ α 1 ⎞ ⎛ β1 ⎞ ⎛ α 2 β 3 − α 3 β 2 ⎞ ⎟ ⎜ ⎟ ⎜ ⎟ ⎜ ⎜ α 2 ⎟ × ⎜ β 2 ⎟ = ⎜ α 3 β1 − α 1 β 3 ⎟ ⎜α ⎟ ⎜ β ⎟ ⎜ α β − α β ⎟ 2 1 ⎠ ⎝ 3⎠ ⎝ 3⎠ ⎝ 1 2 Das Vektorprodukt zweier Vektoren v und w ist ein Vektor. Subtraktion und Vervielfachung) gibt es zudem statische Methoden. In Anwendungen.) Tabelle 12 stellt Ihnen die Methoden der Klasse vor. . Stehen die beiden Vektoren senkrecht zueinander. Vektoren können als Objekte der Klasse erzeugt und bearbeitet werden. Tabelle 11: Vektoroperationen (Forts. indem man die dritte Dimension (Feld z) auf null setzt. ist das Skalarprodukt gleich null.) Die Klasse Vector3D Die Klasse Vector3D ist für die Programmierung mit dreidimensionalen Vektoren ausgelegt. Die drei Vektoren bilden ein Rechtssystem (Drei-Finger-Regel). kann man die Klasse auch für zweidimensionale Vektoren verwenden.>> Zahlen und Mathematik 73 Zahlen Operation Skalarprodukt Beschreibung Das Skalarprodukt (englisch »dot product«) ist das Produkt aus den Längen (Beträgen) zweier Vektoren multipliziert mit dem Kosinus des Winkels zwischen den Vektoren. die das Ergebnis der Operation als neuen Vektor zurückliefern.

Skaliert den übergebenen Vektor um den Faktor s und liefert das Ergebnis als neuen Vektor zurück. Vector3D v2) String toString() Tabelle 12: Methoden der Klasse Vector3D . Berechnet das Vektorprodukt aus dem aktuellen und dem übergebenen Vektor. Addiert den übergebenen Vektor zum aktuellen Vektor. y und z die gleichen Werte wie im aktuellen Vektor haben. wenn das übergebene Objekt vom Typ Vector3D ist und die Felder x.toDegrees() in Grad umgerechnet werden). Berechnet das Skalarprodukt aus dem aktuellen und dem übergebenen Vektor. Berechnet den Winkel zwischen dem aktuellen und dem übergebenen Vektor.0 gesetzt sind. Vector3D v2) double angle(Vector3D v) Object clone() Vector3D crossProduct(Vector3D v) double dotProduct(Vector3D v) boolean equals(Object obj) double length() void scale(double s) static Vector3D scale(Vector3D v. y.74 Zahlen >> Vektoren Methode Vector3D() Vector3D(double x. Der Winkel wird in Bogenmaß zurückgeliefert (und kann beispielsweise mit Math. Die statische Version addiert die beiden übergebenen Vektoren und liefert das Ergebnis als neuen Vektor zurück. Subtrahiert den übergebenen Vektor vom aktuellen Vektor. Der zweite Konstruktor weist den Feldern die übergebenen Werte zu. Der Standardkonstruktor erzeugt einen Vektor. Berechnet die Länge des Vektors. double s) void subtract(Vector3D v) static Vector3D subtract(Vector3D v1.z-Felder auf 0. Liefert true zurück. Skaliert den aktuellen Vektor um den Faktor s. double z) Beschreibung Konstruktoren. Liefert eine String-Darstellung des Vektors zurück: (x. Die statische Version subtrahiert den zweiten vom ersten Vektor und liefert das Ergebnis als neuen Vektor zurück. z) void add(Vector3D v) static Vector3D add(Vector3D v1. dessen x. Zur Überschreibung der equals()-Methode siehe auch Rezept 252. Zur Überschreibung der clone()-Methode siehe auch Rezept 250. Erzeugt eine Kopie des aktuellen Vektors.y. double y.

y.x.x+v2. y *= s. } // Subtraktion public void subtract(Vector3D v) { x -= v.y-v2.y*s.y. v1. } public static Vector3D subtract(Vector3D v1. v.z*s). public double y. public double z. this.y = y. } public static Vector3D scale(Vector3D v.y. } public Vector3D(double x. double y. z += v. } public static Vector3D add(Vector3D v1.x.z.z).x.y+v2. this. } // Skalierung (Multiplikation mit Skalar) public void scale(double s) { x *= s. // Konstruktoren public Vector3D() { x = 0. Vector3D v2) { return new Vector3D(v1. double z) { this.z-v2. v1.java . v1.z = z. z -= v.z. z = 0. v1. } // Addition public void add(Vector3D v) { x += v. } Listing 23: Vector3D. z *= s. double s) { return new Vector3D(v. y -= v.z). y = 0.x = x.x*s.x.z+v2.x-v2. v.>> Zahlen und Mathematik 75 Zahlen /** * Klasse für dreidimensionale Vektoren * */ public class Vector3D implements Cloneable { public double x.y. y += v. Vector3D v2) { return new Vector3D(v1.

76 Zahlen >> Vektoren // Skalarprodukt public double dotProduct(Vector3D v) { return x*v.z*v.y.x + y*v.x*v.z .) .z) return true.y + z*v.y && z == ((Vector3D) obj). z*v.z.sqrt( (x*x + y*y + z*z) * (v.x = x. } catch (CloneNotSupportedException e) { // sollte nicht vorkommen throw new InternalError(). " + z + ")". } // Vektorprodukt public Vector3D crossProduct(Vector3D v) { return new Vector3D(y*v.x*v.z = z.z*v.java (Forts.y . } // Umwandlung in String public String toString() { return "(" + x + ". } } // Vergleichen public boolean equals(Object obj) { if (obj instanceof Vector3D) { if ( x == ((Vector3D) obj).y + v. } // Länge public double length() { return Math.x . v.acos( (x*v.z) / Math. } Listing 23: Vector3D.z. } // Winkel zwischen Vektoren ( arccos(Skalarprodukt/(LängeV1 * LängeV2)) ) public double angle(Vector3D v) { return Math.y + z*v. } // Kopieren public Object clone() { try { Vector3D v = (Vector3D) super.y = y.y*v. return v. v. v.y*v.z))).clone().x + v.sqrt(x*x + y*y + z*z).x). " + y + ".x && y == ((Vector3D) obj).x + y*v. x*v.

println(" Gegeben: Dreieck zwischen Punkten: "). 0)"). a).out. 5. Vector3D b = new Vector3D(2.println("\t B (2.println("\t A (2.println("\n Berechnete Flaeche: " + MoreMath. 2)). 4. public class Start { public static void main(String args[]) { System. 0)"). 5.out. } } Listing 23: Vector3D. Vector3D ac = Vector3D. System.rint siehe Rezept 5 System.rint(area. 4.5 * cross. 0).out.out. 3.length(). Vector3D c = new Vector3D(7.crossProduct(ac)).>> Zahlen und Mathematik 77 Zahlen return false.java (Forts.5. } } Listing 24: Testprogramm: Berechnung eines Flächeninhalts mit Vektoren .println(" Flaecheninhalt eines Dreiecks berechnen").out. System.out.) Das Programm aus Listing Listing 24: demonstriert den Einsatz der Klasse Vector3D anhand eines geometrischen Problems.subtract(c.5. 0).println("\t C (7. 0)"). 0). System. System. System.println().println().out. 3. System.out. a).subtract(b. Mittels Vektoren wird ausgehend von den Punktkoordinaten eines Dreiecks der Flächeninhalt berechnet. // Kantenvektoren Vector3D ab = Vector3D. double area = 0. double cross = (ab. // Punktvektoren Vector3D a = new Vector3D(2. // Für MoreMath.

müssen der gleichen Ordnung angehören. In der Programmierung werden Matrizen vor allem zur Lösung linearer Gleichungssysteme sowie für Vektortransformationen in 3D-Grafikanwendungen eingesetzt. Rechnen mit Matrizen Operation Addition Beschreibung Matrizen werden addiert. indem man ihre einzelnen Komponenten addiert. die subtrahiert werden. die addiert werden. Vervielfachung Vervielfachung ist die Multiplikation mit einem skalaren Faktor. Als (m. 3)-Matrix) A = ⎜ 11 ⎜α ⎟ ⎝ 21 α 22 α 23 ⎠ Matrizen können lineare Abbildungen repräsentieren (eine (m. ⎛ α 11 α12 ⎞ ⎛ β11 ⎜ ⎟+⎜ ⎜α ⎟ ⎜ ⎝ 21 α 22 ⎠ ⎝ β 21 β12 ⎞ ⎛ α11 + β11 α 12 + β12 ⎞ ⎟=⎜ ⎟ β 22 ⎟ ⎜ α 21 + β 21 α 22 + β 22 ⎟ ⎠ ⎝ ⎠ Zwei Matrizen. α 12 ⎞ ⎛ kα11 ⎛α ⎜ ⎟=⎜ k ⎜ 11 α 21 α 22 ⎟ ⎜ kα 21 ⎝ ⎠ ⎝ Tabelle 13: Matrixoperationen kα 12 ⎞ ⎟ kα 22 ⎟ ⎠ . müssen der gleichen Ordnung angehören.n)-Matrix entspricht einer linearen Abbildung vom Vektorraum Vn nach Vm) oder auch lineare Gleichungssysteme. ⎛ α 11 α 12 ⎞ ⎛ β11 ⎜ ⎟−⎜ ⎜α ⎟ ⎜ ⎝ 21 α 22 ⎠ ⎝ β 21 β12 ⎞ ⎛ α 11 − β11 α 12 − β12 ⎞ ⎟=⎜ ⎟ β 22 ⎟ ⎜ α 21 − β 21 α 22 − β 22 ⎟ ⎠ ⎝ ⎠ Zwei Matrizen.n)-Matrix oder Matrix der Ordnung m × n bezeichnet man eine Anordnung aus m Zeilen und n Spalten: α 12 α 13 ⎞ ⎛α ⎟ (Beispiel für eine (2. indem man ihre einzelnen Komponenten subtrahiert.78 Zahlen >> Matrizen Abbildung 13: Ausgabe des Testprogramms 24 Matrizen In der Mathematik ist eine Matrix ein rechteckiges Zahlenschema. Subtraktion Matrizen werden subtrahiert.

können Sie dieses mit der Methode solve() lösen. Die grundlegenden Operationen. dürften aber für die meisten Anwendungen zu viel Laufzeit beanspruchen. Tabelle 14: Methoden der Klasse Matrix .>> Zahlen und Mathematik 79 Zahlen Operation Multiplikation Beschreibung Bei der Matrizenmultiplikation C = A*B ergeben sich die Elemente der Ergebnismatrix C durch Aufsummierung der Produkte aus den Elementen einer Zeile von A mit den Elementen einer Spalte von B: cij = Spalten von A ∑a k =1 ik kj b Eine Multiplikation ist nur möglich. int n. der Invertierten und der Determinanten.n)-Matrix (n Zeilen. double s) Matrix(int m.) Die Klasse Matrix Die Klasse Matrix ist für die Programmierung mit Matrizen beliebiger Ordnung ausgelegt. wenn die Anzahl von Spalten von A gleich der Anzahl Zeilen von B ist. sind zwar allesamt mit der Klasse durchführbar. Die Elemente der Matrix werden je nach Konstruktor mit 0. Die LR-Zerlegung liefert die Methode luDecomp(). die auch direkt aufgerufen werden kann. Erzeugt wird jeweils eine (m. int n.r)Matrix. Ist die Matrix quadratisch und repräsentiert sie ein lineares Gleichungssystem. wie sie für 3D-Grafikanwendungen benötigt werden. m Spalten). Negative Zeilen.oder Spaltendimensionen führen zur Auslösung einer NegativeArraySizeException. Das Ergebnis aus der Multiplikation einer (m. bietet die Klasse nicht. double[][] elems) Beschreibung Konstruktoren. Ansonsten wird eine IllegalArgumentException ausgelöst. siehe beispielsweise Java 3D. ⎛ α 11 α 12 ⎞ ⎛ β11 ⎞ ⎛ (α 11 * β11 ) + (α 12 * β 21 ) ⎞ ⎜ ⎟ ⎜ ⎟ ⎜ ⎟ ⎜α ⎟*⎜ ⎟=⎜ ⎟ ⎝ 21 α 22 ⎠ ⎝ β 21 ⎠ ⎝ (α 21 * β11 ) + (α 22 * β 21 ) ⎠ Tabelle 13: Matrixoperationen (Forts. Sie unterstützt neben den Grundrechenarten auch die Berechnung der Transponierten. (Für professionelle Grafikanwendungen sollten Sie auf eine Implementierung zurückgreifen.) Methode Matrix(int m. die für (4. int n) Matrix(int m. Wird zur Initialisierung ein Array übergeben. um Transformationen rückgängig machen zu können. müssen dessen Dimensionen mit m und n übereinstimmen. Zur Lösung des Gleichungssystems wie auch zur Berechnung der Inversen und der Determinanten wird intern eine LR-Zerlegung der Ausgangsmatrix berechnet. die durch ein Objekt der Hilfsklasse LUMatrix repräsentiert wird. Eine spezielle Unterstützung für Vektortransformationen.4)-Matrizen optimiert ist.0. mit s oder mit den Werten aus dem zweidimensionalen Array elems initialisiert. von der Addition von Transformationen über die Anwendung auf Vektoren durch Matrizenmultiplikation bis hin zur Berechnung der Inversen.r)-Matrix B ist eine (m.n)-Matrix A mit einer (n.

Liefert die Elemente der Matrix als zweidimensionales Array zurück. Erzeugt eine Identitätsmatrix mit m Zeilen und n Spalten.0. Liefert die Anzahl der Spalten (n). Tabelle 14: Methoden der Klasse Matrix (Forts. Liefert den Wert des Elements in Zeile i. Liefert true zurück. Erzeugt eine Kopie der Matrix. Liefert die Anzahl der Zeilen (m).80 Zahlen >> Matrizen Methode void add(Matrix B) static Matrix add(Matrix A.und Skalierungsmatrizen verwendet werden. Bei der 3D-Grafikprogrammierung kann die Identität als Ausgangspunkt zur Erzeugung von Translations. Zur Überschreibung der equals()-Methode siehe auch Rezept 252. int m) int getRowDim() Matrix inverse() Wenn die aktuelle Matrix nicht quadratisch oder singulär ist.m)-Ordnungen an. Matrix B) Beschreibung Addiert die übergebene Matrix zur aktuellen Matrix. Für nichtquadratische oder singuläre Matrizen wird eine IllegalArgumentException ausgelöst. Die statische Version addiert die beiden übergebenen Matrizen und liefert das Ergebnis als neue Matrix zurück. Gehören die Matrizen unterschiedlichen (n. In einer Identitätsmatrix haben alle Diagonalelemente den Wert 1. Liefert die Inverse der aktuellen Matrix zurück.) . wenn das übergebene Objekt vom Typ Matrix ist und die Elemente die gleichen Werte wie die Elemente der aktuellen Matrix haben. Existiert die Inverse. wird eine IllegalArgumentException ausgelöst. Spalte j zurück. wird eine IllegalArgumentException ausgelöst. Liefert die Determinante der aktuellen Matrix zurück. während die restlichen Elemente gleich null sind. int j) double[][] getArray() int getColumnDim() static Matrix getIdentity(int n. Zur Überschreibung der clone()-Methode siehe auch Rezept 250. gilt A*A-1 = I Object clone() boolean equals(Object obj) double det() double get(int i.

⎟ . ⎝ u13 u 23 u33 . für den Vektor B (gegeben als Argument bvec). ⎟ ⎟ . (Siehe auch Rezept 25) void subtract(Matrix B) Subtrahiert die übergebene Matrix von der aktuellen static Matrix subtract(Matrix A.>> Zahlen und Mathematik 81 Zahlen Methode LUMatrix luDecomp() Beschreibung Liefert die LR-Zerlegung der aktuellen Matrix als Objekt der Hilfsklasse LUMatrix zurück. Die LUMatrix hat den folgenden Aufbau: ⎛ u11 u12 ⎜ u 22 ⎜l LU = ⎜ 21 l31 l32 ⎜ ⎜ . ⎞ ⎟ . Die Permutationen sind in den privaten Feldern von LUMatrix gespeichert und werden bei der Rückwärtssubstitution (LUMatrix. Die statische Version subtrahiert die zweite von der ersten Matrix und liefert das Ergebnis als neue Matrix zurück.. Spalte j den Wert s zu. Das Produkt aus L*U liefert nicht direkt die Ausgangsmatrix A. wird eine IllegalArgumentException ausgelöst... die alle 1 sind und daher nicht extra in LU abgespeichert sind. void print() void set(int i. wird eine IllegalArgumentException ausgelöst.... Das zurückgelieferte Array ist der Lösungsvektor X. Tabelle 14: Methoden der Klasse Matrix (Forts. Weist dem Element in Zeile i.out) aus (vornehmlich zum Debuggen und Testen gedacht). dessen Koeffizienten durch die aktuelle (quadratische) Matrix repräsentiert werden.j)-Elemente bilden die obere Dreiecksmatrix U. die l(i..) . Zur LMatrix gehören zudem noch die l(i. Löst das Gleichungssystem. so dass gilt: A*X = B Wenn die aktuelle Matrix nicht quadratisch oder singulär ist..luBacksolve()) berücksichtigt. . . erfolgt nach dem Verfahren von Crout. Matrix multiply(Matrix B) Multipliziert die aktuelle Matrix mit der übergebenen Matrix und liefert das Ergebnis als neue Matrix zurück: Res = Akt * B Wenn die Spaltendimension der aktuellen Matrix nicht gleich der Zeilendimension der übergebenen Matrix ist... Für nichtquadratische oder singuläre Matrizen wird eine IllegalArgumentException ausgelöst.. Die Zerlegung. int j...j)-Elemente die untere Dreiecksmatrix L. ⎟ ⎠ Die u(i.n)-Ordnungen an. die der Konstruktor von LUMatrix vornimmt. Gehören die Matrizen unterschiedlichen (m. double s) double[] solve(double[] bvec) Gibt die Matrix auf die Konsole (System. Matrix B) Matrix. wird eine IllegalArgumentException ausgelöst..i)-Elemente. sondern eine Permutation von A.

i < m. // Leere Matrix (mit 0. } // Konstante Matrix (mit s gefüllt) public Matrix(int m. this. for (int i = 0.DecimalFormat. liefert die Transponierte zur übergebenen Matrix zurück.0 gefüllt) public Matrix(int m. int n.82 Zahlen >> Matrizen Methode void times(double s) static Matrix times(Matrix A. Wenn die zu transponierende Matrix nicht quadratisch ist. class Matrix implements Cloneable { private double[][] elems = null.) /** * Klasse für Matrizen * * @author Dirk Louis */ import java. private int n.m = m.length) // Anzahl Zeilen gleich m? throw new IllegalArgumentException("Fehler in Zeilendimension").m = m. wird eine RuntimeException bzw.n = n. Transponiert die Matrix bzw. sprich durch paarweise Vertauschung der Elemente a(ij) mit a(ji). int n. double[][] elems) { // Dimension des Arrays mit m und n vergleichen if (m != elems. elems = new double[m][n].text. double s) { this. } // Matrix (mit Werten aus zweidimensionalem Array gefüllt) public Matrix(int m. IllegalArgumentException ausgelöst. elems = new double[m][n]. this. der übergebenen Matrix mit dem Faktor s. double s) void transpose() static Matrix transpose(Matrix A) Beschreibung Multipliziert die Elemente der aktuellen bzw.n = n. Tabelle 14: Methoden der Klasse Matrix (Forts.java // Zum Speichern der Elemente // Zeilen-Dimension // Spalten-Dimension . private int m. j++) elems[i][j] = s. i++) for (int j = 0. j < n. Listing 25: Matrix. int n) { this. Die Transponierte ergibt sich durch Spiegelung der Elemente an der Diagonalen.

int n = A. double[][] elemsB = B.n != B. double[][] elemsA = A.m || this.n = n. i < m.m || this. passen nicht. i < m. } // Subtraktion THIS = THIS . for (int i = 0.getColumnDim(). j < n. if (m != B."). this. j < n. i++) for (int j = 0.>> Zahlen und Mathematik 83 Zahlen for (int i = 0. this. return new Matrix(m.length) // gleich m? throw new IllegalArgumentException("Fehler in Spaltendimension").getRowDim() || n != B. i < m."). newElems). j++) newElems[i][j] = elemsA[i][j] + elemsB[i][j].elems = elems.m != B. double[][] subtractElems = B. i++) for (int j = 0.getArray().getArray().getArray(). } // Addition C = A + B public static Matrix add(Matrix A. j < n.) . this. ++i) // für alle Zeilen die Anzahl Spalten if (n != elems[i]."). } // Addition THIS = THIS + B public void add(Matrix B) { if (this.m != B. i < m. for (int i = 0. Matrix B) { int m = A.B public void subtract(Matrix B) { if (this.n) throw new IllegalArgumentException("Matrix-Dim.getArray().m = m.getColumnDim() ) throw new IllegalArgumentException("Matrix-Dim. passen nicht.java (Forts. j++) elems[i][j] -= subtractElems[i][j]. for (int i = 0.getRowDim(). } Listing 25: Matrix. i++) for (int j = 0. j++) elems[i][j] += addElems[i][j]. passen nicht. n.n != B.n) throw new IllegalArgumentException("Matrix-Dim. double[][] newElems = new double[m][n]. double[][] addElems = B.

n. j++) elems[i][j] *= s. i < m.getArray(). j++) newElems[i][j] = elemsA[i][j] .elemsB[i][j].n != B. passen nicht. n. i++) for (int j = 0.getArray(). } // Multiplikation C = THIS * B public Matrix multiply(Matrix B) { if (this. double[][] newElems = new double[m][n].B public static Matrix subtract(Matrix A. passen nicht. i++) for (int j = 0. int n = A.getRowDim()) throw new IllegalArgumentException("Matrix-Dim. Listing 25: Matrix. return new Matrix(m. for (int i = 0. } // Vervielfachung C = B * s public static Matrix times(Matrix B.getRowDim() || n != B.getColumnDim(). } // Vervielfachung THIS = THIS * s public void times(double s) { for (int i = 0. newElems).getRowDim(). int n = B.getColumnDim(). j++) newElems[i][j] = elemsB[i][j] * s. int m = this. for (int i = 0. j < n. i < m.getRowDim().getRowDim(). j < n. double[][] newElems = new double[m][n]. double sum = 0.getColumnDim() ) throw new IllegalArgumentException("Matrix-Dim."). double s) { int m = B. if (m != B. double[][] elemsA = A."). double[][] elemsB = B. j < n. Matrix B) { int m = A.getColumnDim(). return new Matrix(m. double[][] elemsB = B. newElems). int n = B. i++) for (int j = 0. double[][] newElems = new double[m][n].java (Forts.getArray(). i < m.) . double[][] elemsB = B.84 Zahlen >> Matrizen // Subtraktion C = A .getArray().

for (int i = 0.>> Zahlen und Mathematik 85 Zahlen for (int i = 0. j < n. int n) { double[][] idelems = new double[m][n]. i++) for (int j = 0. } // Transponieren C = THIS^T [a(ij) -> c(ji)] public static Matrix transpose(Matrix A) { int m = A. } // Identitätsmatrix C = mxn-I public static Matrix getIdentity(int m. double[][] transelems = new double[m][n].elems[i][j].getColumnDim()."). passen nicht.0 : 0."). j++) { sum = 0. } } return new Matrix(m. j++) transelems[j][i] = this. if (m != n) throw new IllegalArgumentException("Matrix-Dim. j++) idelems[i][j] = (i == j ? 1. this. i++) for (int j = 0. j < n. j < n. i < m. i < m. n. int n = A.n != this.elems[i][k] * elemsB[k][j].elems = transelems. Listing 25: Matrix. } // Transponieren THIS = THIS^T [a(ij) -> a(ji)] public void transpose() { if (this. k++) sum += this. i < m. newElems[i][j] = sum. idelems). for (int i = 0. j++) transelems[j][i] = elemsA[i][j].getColumnDim(). k < this. m. return new Matrix(n. newElems). return new Matrix(m.java (Forts.) .getArray(). j < n. n. i < m. double[][] transelems = new double[m][n]. transelems). double[][] elemsA = A. i++) { for (int j = 0. i++) for (int j = 0. for (int k = 0.0).m) throw new RuntimeException("Matrix-Dimensionen passen nicht. for (int i = 0.getRowDim().

xvec = LU. i< n. j++) { for(int i=0.getD().0. invElems). LUMatrix LU = luDecomp().elems[i][i].luBacksolve(col). j < n. } // Inverse THIS^-1. i++) d *= LU. double[] xvec = LU. for (int j=0. n. i++) col[i] = 0. double d = LU. double[] xvec = new double[n]. } return new Matrix(n. return xvec.java (Forts. i++) invElems[i][j] = xvec[i]. so dass THIS*THIS^-1 = I public Matrix inverse() { double[] col = new double[n]. for (int i=0. i < n.) . col[j] = 1. i< n.86 Zahlen >> Matrizen } // LR-Zerlegung public LUMatrix luDecomp() { return new LUMatrix(this).0. } // Gleichungssystem lösen public double[] solve(double[] bvec) { LUMatrix LU = luDecomp(). double[][] invElems = new double[n][n]. Listing 25: Matrix. } // Determinante public double det() { LUMatrix LU = luDecomp().luBacksolve(bvec). for(int i=0.

} public void set(int i.get(i.) .elems = new double[m][n]. } Listing 25: Matrix. } } // Vergleichen public boolean equals(Object obj) { if (obj instanceof Matrix) { Matrix B = (Matrix) obj. i < m. i < m. } public int getColumnDim() { return n.clone().java (Forts. } // Kopieren public Object clone() { try { Matrix B = (Matrix) super. } public int getRowDim() { return m. i++) for (int j = 0. j < n. return true. int j) { return elems[i][j].>> Zahlen und Mathematik 87 Zahlen return d. j++) if(B.elems[i][j]) return false. for (int i = 0. i++) for (int j = 0. double s) { if( (i >= 0 && i < m) && (j >= 0 && j < n) ) elems[i][j] = s.getRowDim() == m && B.elems[i][j]. } // Get/Set-Methoden public double[][] getArray() { return elems. B. j++) B. } catch (CloneNotSupportedException e) { // sollte nicht vorkommen throw new InternalError().elems[i][j] = this. j) != this. j < n. return B. if (B. } public double get(int i. int j.getColumnDim() == n) { for (int i = 0.

out.java (Forts. { 1.format(elems[i][j]). 4. int maxLength = 0. ++i) { System. 0.printf(MoreString.out. System.##0. Listing 26: Rechnen mit Matrizen . elemsA). {6} }.print (" [ ").print(). 3.out. -3}.format(elems[i][j]).println ("]").out. i < m.out.println("\n multipliziert mit \n"). Matrix A = new Matrix(2.length()) ? maxLength : df. } } } Listing 25: Matrix. for (int j = 0. 1. i < m.println("\n /*** Matrixmultiplikation ***/ \n").out. j < n.print(). } // Ausgeben auf Konsole public void print() { DecimalFormat df = new DecimalFormat("#. public class Start { public static void main(String args[]) { System. Matrix B = new Matrix(3. ++j) System. B. ++j) maxLength = (maxLength >= df.length().strpad(df. // Matrizen aus Arrays erzeugen double[][] elemsA = { { 2. 6} }. System. for (int i = 0.88 Zahlen >> Matrizen return false. System. elemsB). double[][] elemsB = { {1}. j < n.format(elems[i][j]). maxLength) + " " ).println("\n ergibt: \n"). ++i) for (int j = 0. } return false. {2}. A. for (int i = 0.) Das Programm aus Listing 26 demonstriert die Programmierung mit Objekten der Klasse Matrix anhand einer Matrixmultiplikation und der Berechnung einer Inversen.###").

0 }.print().print(). C.out.multiply(B). // Inverse berechnen C = D. Matrix D = new Matrix(2. double[][] elemsD = { { 1.println("\n\n /*** Inverse ***/ \n").println(" Inverse von: \n"). 2} }. D. System.println("\n ist: \n"). 2. C.) Abbildung 14: Matrixmultiplikation und Berechnung der Inversen . } } Listing 26: Rechnen mit Matrizen (Forts.out.out. System. { 1.inverse().out. elemsD). System.>> Zahlen und Mathematik 89 Zahlen // Matrizen multiplizieren Matrix C = A. System.println().print().

Die Klasse selbst ist hier nicht abgedruckt. -3 }. A.println(" z System. System.println(" x System.println(" /*** Lineare Gleichungssysteme ***/ "). // Koeffizientenmatrix erzeugen double[][] elems = { { 1. 1.println("\n").90 Zahlen >> Gleichungssysteme lösen 25 Gleichungssysteme lösen Mit Hilfe der Methode solve() der im vorangehenden Rezept beschriebenen Klasse Matrix können Sie lineare Gleichungssysteme lösen.out.println(" y System. wie mit Hilfe der Klasse Matrix ein lineares Gleichungssystem gelöst werden kann. double[] bvec = { 1. System. System. Intern wird dabei ein Objekt der Hilfsklasse LUMatrix erzeugt.out. die sich durch quadratische Matrizen repräsentieren lassen.out. System.println("\n Koeffizientenmatrix: ").println("\t x + 5y System. System.println("\n System. 1. Das Programm aus Listing 26 demonstriert.println(). 1").println("\t -3x + y System.out. public class Start { public static void main(String args[]) { System. -1}. 1} }.println("\t 3x + y + System.out.println(). } } Listing 27: Testprogramm: Lösung eines linearen Gleichungssystems Gefundene Loesung: "). { 3. Matrix A = new Matrix(3. = " + loesung[1]). 1"). System.println(" Gesucht werden x. Der Code der Klasse ist ausführlich kommentiert.out. // Gleichungssystem lösen double[] loesung = A.println("\n").out. = " + loesung[0]). sodass:\n"). 1.println(). . -3").out.out. System. y z z z und = = = z. steht aber – wie alle anderen zu diesem Buch gehörenden Klassen – als Quelltext auf der Buch-CD zur Verfügung. = " + loesung[2]).out.out.out. 3.out.out. { -3.print(). elems).out. -1}. 5. welches die LUZerlegung der aktuellen Matrix repräsentiert.solve(bvec).

wo manchmal schon geringfügige Rundungs.(BigInteger) oder Gleitkommazahlen (BigDecimal) beliebiger Genauigkeit kapseln. etwa bei quantenphysikalischen Berechnungen oder in der Finanzmathematik.) BigInteger(String zahl) BigDecimal(String zahl) definieren numerische Methoden für die vier Grundrechenarten.>> Zahlen und Mathematik 91 Zahlen Abbildung 15: Lösung eines linearen Gleichungssystems durch Zerlegung der Koeffizientenmatrix 26 Große Zahlen beliebiger Genauigkeit Alle elementaren numerischen Typen arbeiten aufgrund ihrer festen Größe im Speicher mit begrenzter Genauigkeit (wobei die beiden Gleitkommatypen den größeren Integer-Typen sogar bezüglich der Anzahl signifikanter Stellen unterlegen sind). Trotzdem gibt es natürlich Situationen. die Integer. Für solche Fälle stellt Ihnen die Java-Bibliothek die Klassen BigInteger und BigDecimal aus dem Paket java. die als Argument die String-Darstellung der zu kapselnden Zahl erwarten. Beide Klassen arbeiten mit unveränderbaren Objekten. Die Lage ist allerdings bei weitem nicht so tragisch. dass alle diese Methoden das Ergebnis der Operation als neues Objekt zurückliefern (da einmal erzeugte Big-Objekte wie gesagt unveränderlich sind): . wo Wertebereich und Genauigkeit dieser Datentypen nicht ausreichen.oder Darstellungsfehler durch Multiplikation mit großen Faktoren zu extremen Abweichungen führen. definieren neben anderen Konstruktoren auch solche.math zur Verfügung. Beachten Sie. (So lassen sich die zu erzeugenden Instanzen mit Zahlen initialisieren. die nicht mehr als Literale numerischer Datentypen geschrieben werden können. wie es sich anhört: Die Integer-Typen arbeiten in ihrem (für long durchaus beachtlichen) Wertebereich absolut exakt und die Genauigkeit des Datentyps double ist meist mehr als zufrieden stellend.

float interestRate. int term) { return startCapital * Math. Das folgende Programm berechnet das Endkapital gemäß der Zinseszins-Formel Kn = K0 (1 + p/100)n in den drei Datentypen float. double und BigDecimal.25% p.Q. import java.BigDecimal. int term) { return startCapital * (float) Math.math.0 + interestRate.0 + interestRate.92 Zahlen >> Große Zahlen beliebiger Genauigkeit BigInteger add(BigInteger wert) BigInteger subtract(BigInteger wert) BigInteger multiply(BigInteger wert) BigInteger divide(BigInteger wert) BigInteger pow(int exponent) definieren verschiedene weitere Methoden zur Umwandlung in oder aus elementaren Datentypen: long longValue() double doubleValue() static BigInteger valueOf(long wert) definieren Vergleichsmethoden und verschiedene weitere nützliche Methoden (siehe APIDokumentation) boolean equals(Object o) int compareTo(BigInteger wert) Ein Anfangskapital von 200 € soll für 9 Jahre bei einer vierteljährlichen Verzinsung von 0. } /* * Zinseszins-Berechnung mit BigDecimal-Werten */ Listing 28: Rechnen mit BigDecimal . angelegt werden. double interestRate. public class Start { /* * Zinseszins-Berechnung mit float-Werten */ static float compoundInterest(float startCapital.pow(1.pow(1. } /* * Zinseszins-Berechnung mit double-Werten */ static double compoundInterest(double startCapital. term). term).

println(compoundInterest(200.0f. 36)).36).025f. Achtung . for (int i = 1. mit erhöhter Genauigkeit berechnet wurde.5070631435786 486.add(new BigDecimal(1. } public static void main(String args[]) { System.0"). Er ist aber nicht so tragisch. } } Listing 28: Rechnen mit BigDecimal (Forts.0. int term) { interestRate = interestRate. BigDecimal interestRate.025"). System. new BigDecimal("0.println().025. Den mit der Umwandlung einhergehenden Genauigkeitsverlust müssen wir also in Kauf nehmen. return startCapital.multiply(factor). dass die Formel (insbesondere die Potenz!).out. aus der der tatsächliche Wert praktisch nicht herauszulesen ist. System. BigDecimal factor = new BigDecimal(0).doubleValue()).multiply(interestRate).50706314358007 Für die Ausgabe des BigDecimal-Werts ist die Umwandlung in double notwendig.50708 486.add(interestRate).out.out.println(compoundInterest(200. System. da toString() eine Stringdarstellung des BigDecimal-Objekts liefert. Entscheidend ist. factor = factor. i < term . 0.println(compoundInterest(new BigDecimal("200.>> Zahlen und Mathematik 93 Zahlen static BigDecimal compoundInterest(BigDecimal startCapital.0)).out.) Ausgabe: 486. 0. ++i) factor = factor. 36)).

.

. Als Argument übergeben Sie das zu suchende Zeichen und optional die Position. Die wichtigsten Einsatzmöglichkeiten sind die Suche nach dem ersten Vorkommen eines Zeichens: // Erstes Vorkommen von 'y' in String text int pos = text. ++found. Mit Hilfe der String-Methoden indexOf() und substring() ist eine solche Methode aber schnell implementiert: Strings .indexOf('y'. found)) != -1 ) { // hier Vorkommen an Position end verarbeiten .. die Suche nach allen Vorkommen eines Zeichens: // Alle Vorkommen von 'y' in String text int found = 0. Teilstrings. Grundsätzlich gilt. } Suchen nach Teilstrings Ebenso wichtig wie die Suche nach Zeichen ist die Suche nach Teilstrings in einem String. werden sie grundsätzlich sequentiell (von vorn nach hinten oder umgekehrt von hinten nach vorn) durchsucht: Trotzdem lassen sich drei alternative Suchverfahren unterscheiden. while ((found = text. Als Ergebnis erhalten Sie die Position des nächsten gefundenen Vorkommens. wobei die Methode indexOf() den String von vorn nach hinten und die Methode lastIndexOf() von hinten nach vorn durchsucht. ab der gesucht werden soll. nämlich die Suche nach einzelnen Zeichen. mit der man einen String nach den Vorkommen eines Teilstrings durchsuchen könnte. die Suche nach dem letzten Vorkommen eines Zeichens: // Letztes Vorkommen von 'y' in String text int pos = text.indexOf('y'). aber auch immer teurer werden. Doch leider gibt es in der String-Klasse derzeit keine Methode. dass die aufgeführten Suchverfahren von oben nach unten immer leistungsfähiger. Suchen nach einzelnen Zeichen Mit den String-Methoden indexOf() und lastIndexOf() können Sie nach einzelnen Zeichen in einem String suchen.lastIndexOf('y').Strings 27 In Strings suchen Da Strings nicht sortiert sind. Mustern (regulären Ausdrücken).

indexOf(searched. if (tmp.length(). String searched. Eingesetzt wird die Methode so wie indexOf(): // Erstes Vorkommen von "John Maynard" in String text int pos = MoreString. int found = pos. der ebenso groß ist wie der gesuchte String (sofern die Länge von text dies zulässt). Wird kein Vorkommen von searched gefunden. Wurde ein Vorkommen dieses Buchstabens gefunden. kopiert die Methode ab seiner Position aus text einen String heraus.substring(found. Diese zweite Methode übernimmt als zusätzliches Argument besagte Positionsangabe und durchsucht dann ab dieser Position den String text nach dem nächsten Vorkommen von searched. found + lenSearched). und vergleicht diesen mit searched.96 Strings >> In Strings suchen /** * String nach Teilstrings durchsuchen */ public static int indexOfString(String text. . Teilstring herauskopieren // und mit dem gesuchten String vergleichen if (found + lenSearched <= lenText) { tmp = text. } public static int indexOfString(String text. Sie geht dabei so vor. 0). } return found. // Nach Anfangsbuchstaben suchen while ((found = text. searched. String searched) { return indexOfString(text. "John Maynard").indexOfString(text. Stimmen beide Strings überein. // Alle Vorkommen von "John Maynard" in String text found = 0.h. int lenSearched = searched. sie ruft einfach die zweite Methode mit der Startposition 0 auf.charAt(0). } Listing 29: Methoden zum Suchen nach Strings in Strings Die erste der beiden Methoden übernimmt als Argumente den zu durchsuchenden String und den zu suchenden String und beginnt mit der Suche am Anfang des Strings. int lenText = text.length(). found)) != -1 ) { // Wenn String noch groß genug.equals(searched)) break. dass sie zuerst mit indexOf() nach dem Anfangsbuchstaben von searched sucht. int pos) { String tmp. // Vorkommen gefunden } ++found. wird die Suche abgebrochen und die gefundene Position zurückgeliefert. d.. liefert die Methode -1 zurück.

"John Maynard". import java.regex. m. Pattern pat = Pattern.matcher(text).>> Strings while((found = MoreString.compile("[A-ZÄÖÜ][a-zA-ZäöüßÄÖÜ]+"). rufen Sie reset() auf. Wenn Sie die Suche an einer bestimmten Position starten wollen. Matcher m = pat. found)) != -1) { // hier Vorkommen an Position end verarbeiten . 1. gegebenenfalls nach m.console(). Schließlich lassen Sie die Matcher-Methode find() das nächste Vorkommen suchen. Um die Suche wieder am Anfang zu beginnen.printf("Erstes Vorkommen: %s\n".indexOfString(text. Achtung! Die Methode find() setzt die Suche automatisch immer an der Position fort.. m. Dann kompilieren Sie das Muster in ein Pattern-Objekt und besorgen sich für das PatternObjekt und den zu durchsuchenden String einen Matcher. ++found. Beispielsweise könnten Sie einen deutschen Text mit folgendem Muster nach Substantiven durchsuchen: "[A-ZÄÖÜ][a-zA-ZäöüßÄÖÜ]+" Dieses Muster beschreibt ein Wort.reset(). nach dem gesucht werden soll. übergeben Sie find() als zweites Argument die gewünschte Position.group()). Sie prüft lediglich.Pattern. } Im Start-Programm zu diesem Rezept werden die hier vorgestellten Suchverfahren alle noch einmal eingesetzt und demonstriert. Zuerst definieren Sie das Muster. dem beliebig viele Kleinbuchstaben folgen. // Alle Vorkommen suchen.regex. das mit einem Großbuchstaben beginnt.. while (m... A c h t u ng Die String-Methode matches() ist nicht zum Durchsuchen von Strings geeignet.find()) System. 3.Matcher.console(). // Erstes Vorkommen suchen if (m. } 97 Strings Suchen nach Mustern Mit Hilfe der RegEx-Unterstützung von Java können Sie auch nach Vorkommen eines Musters suchen. ob der aktuelle String einem übergebenen Muster entspricht! . an der die letzte find()-Suche beendet wurde.find()) { System.group()). 2.util. Das letzte gefundene Vorkommen wird intern vom Matcher-Objekt gespeichert und kann durch Aufruf der Methode group() abgefragt werden. . import java.util.printf("%s\n".

char c) String str) boolean b) int i) auf. offset. sondern spielt lediglich – in Anlehnung an den »Lotsen« Bismarck – mit der Vorstellung vom Kanzler als Steuermann. String str) auf.toString(). Das Start-Programm zu diesem Rezept demonstriert die Ersetzung mit der replace()-Methode von StringBuilder und der replaceAll()-Methode von String. offset. Da StringBuilder-Objekte nicht wie String-Objekte immutable sind. wandeln Sie den String in ein StringBuilder-Objekt um und rufen Sie eine der überladenen Versionen von StringBuilder StringBuilder StringBuilder StringBuilder . (Hinweis: Das Programm ist trotz der Erwähnung eines bekannten Politikers nicht als politischer Kommentar gedacht. insert(int insert(int insert(int insert(int offset.. Der resultierende String wird als Ergebnis zurückgeliefert. Den zugehörigen String können Sie sich durch Aufruf von toString() zurückliefern lassen.98 Strings >> In Strings einfügen und ersetzen 28 In Strings einfügen und ersetzen Wenn Sie alle Vorkommen eines Zeichens durch ein anderes Zeichen ersetzen wollen. String replacement) auf und übergeben Sie das Muster als erstes Argument. rufen Sie die String-Methode String replaceAll(String regex. String replacement) auf. Den zugehörigen String können Sie sich durch Aufruf von toString() zurückliefern lassen: StringBuilder tmp = new StringBuilder(text). Wenn Sie alle Vorkommen eines Teilstrings durch einen anderen Teilstring ersetzen wollen. Der resultierende String wird als Ergebnis zurückgeliefert. text = tmp. bearbeitet die StringBuilder-Methode direkt das aktuelle Objekt. Der resultierende String wird als Ergebnis zurückgeliefert. offset. 10. Wenn Sie alle Vorkommen eines Musters durch einen anderen Teilstring ersetzen wollen.. char newChar) auf. int end.) .replace(0. Wenn Sie Zeichen oder Strings an einer bestimmten Position in den String einfügen wollen. rufen Sie die String-Methode String replaceAll(String regex. Als erstes Argument übergeben Sie einfach den zu ersetzenden Teilstring. wandeln Sie den String in ein StringBuilder-Objekt um und rufen Sie die Methode StringBuilder replace(int start. Wenn Sie die Zeichen von start bis einschließlich end-1 durch einen anderen Teilstring ersetzen wollen. " "). rufen Sie die String-Methode String replace(char oldChar. tmp.

// Zeichen von 156 bis einschließlich 160 ersetzen StringBuilder tmp = new StringBuilder(text).\n" "John Maynard. int found.toString(). // Originaltext ausgeben System. // Bearbeiteten Text ausgeben System.console()."tat es"). text).\"\n". } } Listing 30: Ersetzen in Strings Abbildung 16: Textfälschung mittels replace() . public class Start { public static void main(String args[]) { String text = + + + + + + int pos.printf("\n%s".\n" "Er starb fuer uns.\n" "Aus hielt er.replaceAll("John Maynard". text = tmp.>> Strings 99 Strings "John Maynard!\n" "\"Wer ist John Maynard?\"\n" "\"John Maynard war unser Steuermann.console(). text).\n" "Er hat uns gerettet.replace(156. er traegt die Kron. "Gerhard Schroeder"). tmp. // "John Maynard" durch "Gerhard Schroeder" ersetzen text = text. bis er das Ufer gewann.161. unsre Liebe sein Lohn.printf("\n%s".

// erkennt Semikolon und // Schrägstrich als Trennmarkierung Abbildung 17: Beispiel für die Zerlegung eines Textes in einzelne Wörter oder beliebig komplexe Trennmarkierungen definieren: . Für solche Fälle ist split() ideal.-90".5623."). String[] buf = data. die durch spezielle Zeichen oder Zeichenfolgen getrennt sind./]"). die extrahiert werden sollen. der mehrere Zahlenwerte enthält. können Sie sogar durch Definition einer passenden Zeichenklasse nach mehreren alternativen Trennmarkierungen suchen lassen: String[] buf = data. die durch Semikolon getrennt sind: String data = "1. Selbstverständlich können Sie auch Strings aus mehreren Zeichen als Trennmarkierung übergeben. Die Semikolons dienen lediglich als Trennzeichen und sollen bei der Extraktion verworfen werden (d. Ein gutes Beispiel ist ein String. dass der zu zerlegende Text aus mehreren informationstragenden Passagen besteht. Die Zahlen sind in diesem Fall die eigentlich interessierenden Passagen. String[] split(String regex) Der split()-Methode liegt die Vorstellung zugrunde.100 >> Strings zerlegen Strings 29 Strings zerlegen Zum Zerlegen von Strings steht die String-Methode split() zur Verfügung.-234. sie sollen nicht mehr als Teil der zurückgelieferten Strings auftauchen).split(".split("[.h.. Da der übergebene String als regulärer Ausdruck interpretiert wird. Sie übergeben einfach das Trennzeichen (in Form eines Strings aus einem Zeichen) und erhalten die zwischen den Trennzeichen stehenden Textpassagen als String-Array zurück.

split("\\s+"). die dem String zugrunde liegt) operieren und mit deren Hilfe Konkatenations-. der sich durch die Anfertigung der Kopie ergibt. die direkt auf dem aktuellen Objekt (letzten Endes also der Zeichenkette. werden die Zeichen des zweiten Strings nicht einfach an das letzte Zeichen des ersten Strings angefügt. sprich unveränderlich. ".". verschmerzbar. Diese stellen Methoden zur Verfügung. Zu beachten ist allerdings.java // Text. ist der Overhead.\n so will ich dir die Krone des " + "Lebens geben. String partThree = "dass wir uns über uns selbst täuschen. ". Java definiert zu diesem Zweck die Klassen StringBuilder und StringBuffer. text. da String-Objekte immutable. Sobald aber auf einen String mehrere manipulierende Operationen nacheinander ausgeführt werden. String[] words = text. werden daher nicht auf den Originalstrings. // String-Builder-Objekt in String verwandeln String s = text. sind.".append(partTwo). Einfügeund Ersetzungsoperationen effizient durchgeführt werden können.toString(). dass diese Operationen. Solange die entsprechenden String-Manipulationen nur gelegentlich durchgeführt werden. // Beliebige Folgen von Whitespace // als Trennmarkierung erkennen Listing 31: Strings mit split() zerlegen 30 Strings zusammenfügen Dass Strings mit Hilfe des +-Operators oder der String-Methode concat() aneinander gehängt werden können (Konkatenation).>> Strings 101 Strings // aus Start. String partOne = "Der Grund.append(partThree). warum wir uns über die Welt täuschen. ist allgemein bekannt. die den ursprünglichen String verändern würden. sondern es wird ein ganz neues String-Objekt erzeugt. vergleichsweise kostspielig sind. stellt sich die Frage nach einer ressourcenschonenderen Vorgehensweise. // StringBuilder-Objekt auf der Grundlage von partOne erzeugen StringBuilder text = new StringBuilder(partOne). wie im Übrigen sämtliche Manipulationen von String-Objekten. sondern einer Kopie ausgeführt! Wenn Sie also – um ein konkretes Beispiel zu geben – an einen bestehenden String einen anderen String anhängen. Listing 32: String-Konkatenation mit StringBuilder . // Text in StringBuilder-Objekt bearbeiten text. der mit Zeilenumbrüchen und zusätzlichen Leerzeichen formatiert wurde String text = "Sei getreu bis in den Tod. Alle String-Operationen. in welches die Zeichen der beiden aneinander zu hängenden Strings kopiert werden. String partTwo = "liegt sehr oft darin.

dass der Geschwindigkeitsvorteil von StringBuilder. Die folgenden Rezepte sollen helfen. Denken Sie aber auch daran. So vorteilhaft es ist. (Gilt natürlich ebenso für die umgekehrte Richtung. sie eignet sich für Implementierungen.h. Für String-Vergleiche gibt es in Java auf der einen Seite die String-Methoden compareTo() und compareToIgnoreCase(). ohne große Mühen nachträglich auf StringBuffer umgestellt werden: Sie müssen lediglich alle Vorkommen von StringBuilder durch StringBuffer ersetzen. dürfen Sie nicht vergessen.. sind Convenience-Methoden. Je länger der Ausgangsstring ist. Mindestens ebenso wichtig ist.append() nicht nur darin besteht. Diese Methoden vergleichen immer ganze Strings. wo Strings nur innerhalb eines Threads benutzt werden.) E x ku r s 31 Strings nach den ersten n Zeichen vergleichen Die Java-String-Klassen bieten relativ wenig Unterstützung zur bequemen Textverarbeitung. müssen Sie die zu vergleichenden String-Teile als Teilstrings aus den Originalstrings herausziehen (substring()Methode). dass die Zeichen des Ausgangsstrings (gemeint ist der String. sollten Sie stets StringBuilder verwenden. Die Klasse StringBuffer wurde bereits in Java 1. was fehlt. und auf der anderen Seite die Collator-Methode compare() für Vergleiche gemäß einer Lokale (siehe Rezept 205). wie man sie von stärker textorientierten Programmiersprachen kennt und die einem die eine oder andere Aufgabe vereinfachen würden. Sofern Sie also nicht mit einem älteren JDK (vor Version 1. in den 90% der Fälle. . d. wie viel Zeit 10. Das folgende Listing demonstriert dies und kapselt den Code gleichzeitig in eine Methode. den unnötigen Ballast der Synchronisierung mitzuschleppen. um es effizienter bearbeiten zu können. eine synchronisierte Klasse für String-Manipulationen zur Verfügung zu haben. umso mehr Zeit wird also eingespart. dass elementare Funktionalität fehlen würde.2 eingeführt und ist synchronisiert.102 >> Strings nach den ersten n Zeichen vergleichen Strings Wenn Sie einen String in ein StringBuilder-Objekt verwandeln wollen. der mit StringBuilder arbeitet. auf die von mehreren Threads aus zugegriffen wird. Mit dem Start-Programm zu diesem Rezept können Sie messen. die nach dem Unicode der Zeichen vergleichen. an den angehängt wird) nicht mehr kopiert werden müssen. Im Übrigen kann Code. so ärgerlich ist es. Nicht. Wenn Sie lediglich die Anfänge zweier Strings vergleichen wollen. in denen mehrere Threads gleichzeitig auf einen String zugreifen. StringBuilder oder StringBuffer? Die Klassen StringBuilder und StringBuffer verfügen über praktisch identische Konstruktoren und Methoden und erlauben direkte Operationen auf den ihnen zugrunde liegenden Strings. Aus diesem Grund wurde in Java 5 StringBuilder eingeführt – als nichtsynchronisiertes Pendant zu StringBuffer.5) arbeiten oder Strings benötigen. diese Lücke zu schließen.000 StringKonkatenationen mit dem String-Operator + und mit der StringBuilder-Methode append() benötigen. dass kein neues String-Objekt erzeugt werden muss. die Kosten für die Umwandlung in StringBuilder und wieder zurück in String in Ihre Kosten-Nutzen-Analyse mit einzubeziehen.

Listing 34: Strings lexikografisch nach den ersten n Zeichen vergleichen .length() > n) s1 = s1. muss der Vergleich mit Hilfe von Collator. Folglich übernimmt sie auch die compareTo()-Semantik: Der Rückgabewert ist kleiner.substring(0.substring(0. 0 oder 1 */ public static int compareN(String s1. // Die Stringanfänge vergleichen return s1.length() > n) s2 = s2. wenn String mehr als n Zeichen enthält if (s1.compareTo(s2). if (s1 == null || s2 == null) throw new IllegalArgumentException(). Locale loc) { if (n < 1) throw new IllegalArgumentException(). if (s1 == null || s2 == null) throw new IllegalArgumentException(). if (s2. int n) { if (n < 1) throw new IllegalArgumentException(). gleich oder größer null. String s2. if (s2.length() > n) s1 = s1. je nachdem. ob der erste String kleiner. gleich oder größer als der zweite String ist.length() > n) s2 = s2. wenn String mehr als n Zeichen enthält if (s1. n).substring(0. // Kürzen. int n. der Stringlängen. String s2.>> Strings 103 Strings /** * Strings nach ersten n Zeichen vergleichen * Rückgabewert ist kleiner. // Kürzen.substring(0. falls die Zeichenpaare alle gleich sind). n). Sollen die Strings lexikografisch verglichen werden. gleich oder größer Null */ public static int compareN(String s1. } Listing 33: Strings nach den ersten n Zeichen vergleichen Die Methode compareN() ruft für den eigentlichen Vergleich die String-Methode compareTo() auf. n). n). Verglichen wird nach dem Unicode und der zurückgelieferte Zahlenwert gibt die Differenz zwischen den Unicode-Werten des ersten unterschiedlichen Zeichenpaars an (bzw. compare() durchgeführt werden: /** * Strings nach ersten n Zeichen gemäß Lokale vergleichen * Rückgabewert ist -1.

util.exit(0). args[1]. mit Berücksichtigung der Groß-/Kleinschreibung.println(). } else { System. mit denen Strings ohne Berücksichtigung der Groß-/Kleinschreibung verglichen werden können: static int compareNIgnoreCase(String s1. ohne Berücksichtigung der Groß-/Kleinschreibung.println(" String 1 ist groesser").out.out. } Listing 35: Testprogramm für String-Vergleiche .getInstance(loc). String s2. gemäß der deutschen Lokale. ohne Berücksichtigung der Groß-/Kleinschreibung. if (args. } Listing 34: Strings lexikografisch nach den ersten n Zeichen vergleichen (Forts.compareN(args[0]. System. String s2.) H in we is In der Datei MoreString. } try { int n = Integer.out.out.out. mit Berücksichtigung der Groß-/Kleinschreibung. int n) Das Start-Programm zu diesem Rezept liest über die Befehlszeile zwei Strings und die Anzahl der zu vergleichenden Zeichen ein.104 >> Strings nach den ersten n Zeichen vergleichen Strings Collator coll = Collator. int n. if(erg < 0) { System.println(" String 1 ist kleiner"). Dann vergleicht das Programm die beiden Strings auf vier verschiedene Weisen: gemäß Unicode. gemäß Unicode.println(" Strings sind gleich"). s2). gemäß der deutschen Lokale.out.parseInt(args[2]).compare(s1. return coll. } else if(erg == 0) { System.length != 3) { System. import java. n). Locale loc) static int compareNIgnoreCase(String s1.println(" Aufruf: Start <String> <String> <Ganzzahl>"). int erg = MoreString. public class Start { public static void main(String args[]) { System. System.java zu diesem Rezept sind noch zwei weitere Methoden definiert.Locale.println("\n Vergleich nach Unicode ").

compareN(args[0]. switch(erg) { case -1: System. case 0: System. System. Wesentlich effizienter wäre es.out.) Abbildung 18: Ob ein String größer oder kleiner als ein anderer String ist. } .out..out. man muss es so hart sagen. wenn Sie wirklich exzessiven Gebrauch von diesen Methoden machen wollen.>> Strings 105 Strings . n.err.. T ip p . args[1]. break. hängt vor allem von der Vergleichsmethode ab! Die in diesem Rezept vorgestellten Methoden sind. break. "DE")).println(" String 1 ist kleiner"). alles andere als effizient implementiert: Die Rückführung auf vorhandene API-Klassen sorgt für eine saubere Implementierung..println(" String 1 ist groesser"). die Strings in char-Arrays zu verwandeln (String-Methode toCharArray()) und diese selbst Zeichen für Zeichen zu vergleichen. } catch (NumberFormatException e) { System. erg = MoreString.println(" Strings sind gleich"). } } } Listing 35: Testprogramm für String-Vergleiche (Forts. bedeutet aber zusätzlichen Function Overhead. und die Manipulation der tatsächlich ja unveränderbaren String-Objekte führt im Hintergrund zu versteckten Kopieraktionen.println("\n Vergleich nach Lokale"). break..println(" Ungueltiges Argument").out. case 1: System. Der Aufwand lohnt sich aber nur. new Locale("de".

return new String(tmp). int n) { if (n > 0) { char[] tmp = new char[n]. ist allerdings lästig und stört die Lesbarkeit des Textverarbeitungscodes. dass die Methode nicht einfach ein leeres String-Objekt erzeugt und diesem mit Hilfe des +-Operators n Mal das Zeichen c anhängt. eventuell auch die Umwandlung in StringBuilderObjekte. Die Methode charNTimes() erzeugt einen String. c).fill(tmp. Jegliche Änderung an einem String-Objekt.106 >> Zeichen (Strings) vervielfachen Strings 32 Zeichen (Strings) vervielfachen Strings zu vervielfachen oder ein Zeichen n Mal in einen String einzufügen. Arrays.util. int n) { StringBuilder tmp = new StringBuilder(). Zur Erinnerung: String-Objekte sind immutable. der aus n Zeichen c besteht. Convenience-Methoden. Stattdessen wird ein char-Array passender Größe angelegt und mit dem Zeichen c gefüllt. der aus n Kopien des Strings s besteht. } else return "". /** * String vervielfältigen */ public static String strNTimes(String s. A c h t u ng Die Schwestermethode heißt strNTimes() und erzeugt einen String. /** * Zeichen vervielfältigen */ public static String charNTimes(char c. sei es durch den +-Operator oder eine der String-Methoden führt dazu. ist nicht schwer. import java. dass ein neues String-Objekt mit den gewünschten Änderungen erzeugt wird.h. } Listing 36: String aus n gleichen Zeichen erzeugen Beachten Sie. dass n+1 String-Objekte erzeugt würden. Anschließend wird das Array in einen String umgewandelt und als Ergebnis zurückgeliefert. d. unveränderbar. Strings vervielfachen und als String zurückliefern.Arrays. können hier Abhilfe schaffen. Das Aufsetzen der nötigen Schleifen. die Zeichen bzw. Diese Vorgehensweise würde wegen der Unveränderbarkeit von String-Objekten dazu führen. Listing 37: String aus n Kopien einer Zeichenfolge erzeugen .

i <= n.>> Strings 107 Strings for(int i = 1. um Strings an den Enden bis zu einer gewünschten Länge aufzufüllen. die Methoden dieser Klassen operieren auf dem aktuellen Objekt und verändern dessen Zeichenfolge (statt wie im Fall von String ein neues Objekt zu erzeugen). weil sie im Gegensatz zu den StringBuffer-Methoden nicht threadsicher sind.println("\t Zeichenfolgen vervielfachen ").println().println("\t Zeichen und ").println(" " + MoreString. 40)). System.out.strNTimes("*-".toString(). d.) Diese Methode arbeitet intern mit einem StringBuilder-Objekt. aber keine Methode. System.. .out. ++i) tmp. Das Füllzeichen ist frei wählbar.out.append(s). } Listing 37: String aus n Kopien einer Zeichenfolge erzeugen (Forts. public class Start { public static void main(String args[]) { System. } } Listing 38: Testprogramm zu charNTimes() und strNTimes() 33 Strings an Enden auffüllen (Padding) Die Klasse String definiert eine Methode trim() zum Entfernen von Whitespace-Zeichen an den Enden eines Strings. Die zurückgelieferten Strings können Sie in andere Strings einbauen oder direkt ausgeben. return tmp.h.und StringBuffer-Objekte erlauben die direkte Manipulation von Strings.out. Der Parameter end bestimmt.PADDING_RIGHT übergeben werden.out. wird der String am Ende gekürzt. wie vorzugehen ist. füllt ihn bis auf die gewünschte Zeichenlänge auf und liefert den resultierenden String zurück. Als Argumente können ihm die vordefinierten Konstanten MoreString. Ist cut gleich true. System.charNTimes('*'. Die statische Methode MoreString.println(" /" + MoreString. H i nwe i s StringBuilder.println().PADDING_LEFT und MoreString. System.strpad() übernimmt einen String. Die Klassen StringBuilder und StringBuffer besitzen identische Methoden. Die StringBuilder-Methoden sind in der Ausführung allerdings schneller. wenn die gewünschte Länge kleiner als die Originallänge des Strings ist.out. 20) + "/").println(). ob am Anfang oder Ende des Strings aufgefüllt wird. ansonsten wird der Originalstring zurückgeliefert. Der letzte Parameter cut legt fest. um die Mehrfacherzeugung von String-Objekten zu vermeiden.out. System. System.

++i) pad[i] = c. } . else return s + new String(pad). i < pad. boolean cut) { if(length < 1 || s.s.substring(0. char c. /** * Padding (Auffüllen) für String */ public static String strpad(String s. Listing 39: String auf gewünschte Länge auffüllen // String vergrößern .length() > length) if (cut) // String verkleinern return s. if(end == MoreString.108 >> Strings an Enden auffüllen (Padding) Strings Abbildung 19: Ausgabe vervielfachter Zeichen und Zeichenfolgen public class MoreString { public static final short PADDING_LEFT = 0. int length. else // String unverändert zurückgeben return s. char[] pad = new char[diff].. short end.length.length() == length) return s. .PADDING_LEFT) return new String(pad) + s. // Differenz berechnen int diff = length . public static final short PADDING_RIGHT = 1....length(). for(int i = 0. length). if(s.

System.println(MoreString. } } Listing 40: Testprogramm zu strpad() Abbildung 20: String-Padding .strpad(s. System. String s = "Text".out. System.out.out. 5.out.PADDING_LEFT).println(MoreString. System. 9.out.out.strpad(s.println(MoreString. 7)). 3)).out. 5)).PADDING_RIGHT)). System. System.out.out. System. 9)). 7 " + "und 9"). System. 7.' '.'. length.strpad(s. System.strpad(s.println(MoreString. MoreString.strpad(s.PADDING_RIGHT)). . 7 und 9"). System.out.println().MoreString.MoreString.'.println(" Padding rechts mit System.'.println(). } Listing 40 demonstriert den Einsatz der Methode.'.println(MoreString.out. int length) { return MoreString. System.'. public class Start { public static void main(String args[]) { System.MoreString.'.out.out.out. Für diese spezielle Aufgabe gibt es eine überladene Version der Methode: public static String strpad(String s. 5.PADDING_RIGHT)).println(MoreString.println(" Padding links mit Leerzeichen auf Laengen 3.strpad(s. System.println().println(). System.'.println().MoreString.strpad(s.'.PADDING_RIGHT)). 5.strpad(s.println(MoreString.strpad(s.>> Strings 109 Strings Häufig müssen Strings linksseitig mit Leerzeichen aufgefüllt werden.println(MoreString. 3.out. auf Laengen 3.

toCharArray().length().length()) ? s. Dieses Array gehen die Methoden in einer while-Schleife Zeichen für Zeichen durch: ltrim() vom Anfang und rtrim() vom Ende ausgehend. Stringanfang wie -ende. Zeilenumbruch. die trim() verweigert. den Whitespace abschneidet.1] <= ' ')) { --len.bzw. bis Nicht-Whitespace-Zeichen // (Unicode > Unicode von ' ') oder Stringende erreicht while ((i < len) && (chars[i] <= ' ')) { ++i.110 >> Whitespace am String-Anfang oder -Ende entfernen Strings 34 Whitespace am String-Anfang oder -Ende entfernen Zu den Standardaufgaben der String-Verarbeitung gehört auch das Entfernen von Whitespace (Leerzeichen.). Um Ihnen die Wahlmöglichkeit wiederzugeben. mit denen Sie Whitespace gezielt vom String-Anfang (ltrim()) bzw. das dem übergebenen String zugrunde liegt. } Listing 41: Methoden zur links. int i = 0. } /** * Whitespace vom Stringende entfernen */ public static String rtrim(String s) { int len = s. Die Schleife wird so lange fortgesetzt. char[] chars = s. char[] chars = s. rechtsseitigen Entfernung von Whitespace Beide Methoden lassen sich von der String-Methode toCharArray() das Zeichenarray zurückliefern. /** * Whitespace vom Stringanfang entfernen */ public static String ltrim(String s) { int len = s. bis Nicht-Whitespace-Zeichen // (Unicode > Unicode von ' ') oder Stringanfang erreicht while ((len > 0) && (chars[len . } // gekürzten String zurückliefern return (i > 0) ? s.substring(0. len) : s. len) : s. Die String-Klasse stellt zu diesem Zweck die Methode trim() zur Verfügung – allerdings mit dem kleinen Wermutstropfen.substring(i. // Länge len verkürzen. dass diese immer von beiden Seiten.toCharArray(). String-Ende (rtrim()) entfernen können. } // gekürzten String zurückliefern return (len < s. erhalten Sie hier zwei Methoden ltrim() und rtrim(). wie der Unicode-Wert der vorgefundenen Zeichen . // Index i vorrücken.length(). Tabulatoren etc.

System. ein vom Whitespace befreiter Teilstring oder der Originalstring zurückgeliefert. es gibt auch eine Arrays-Methode toString().println(ints). 7005}. Und richtig. Abbildung 21: Effekt der Methoden ltrim() und rtrim() 35 Arrays in Strings umwandeln Als Java-Programmierer denkt man bei der Umwandlung von Objekten in Strings natürlich zuerst an die Methode toString(). dass man sich von der Ausgabe eines Arrays in der Regel verspricht.out.println(Arrays. übergeben Sie den String einfach als Argument an die jeweilige Methode und nehmen den bearbeiteten String als return-Wert entgegen: String trimmed = MoreString. int[] ints = { 1. 55. Anschließend wird je nachdem.tostring() auf Ausgabe: [I@187c6c7 Angesichts der Tatsache.toString() Die Arrays-Methode toString() übernimmt als Argument ein beliebiges Array und liefert als Ergebnis einen String mit den Elementen des Arrays zurück. dass die Array-Unterstützung von Java nicht in den Array-Objekten selbst. Genauer gesagt: Die Elemente im Array werden einzeln in Strings umgewandelt. 45. sondern in der Utility-Klasse Arrays implementiert ist. Ausgabe: [1. ist die Performance von toString() enttäuschend. -312. 7005] . Um einen String mit Hilfe dieser Methoden am Anfang oder Ende von Whitespace zu befreien. in Strings umgewandelt und aneinander gereiht werden. -9. dass die einzelnen Array-Elemente in der Reihenfolge. -9. System. // ruft intern ints. Andererseits ist bekannt. 45. 7005}. Arrays. 55. -312.toString(ints)). 55. durch Komma und Leerzeichen getrennt aneinander gehängt.out. ob Whitespace gefunden wurde oder nicht. -9. -312.>> Strings 111 Strings kleiner oder gleich dem Unicode-Wert des Leerzeichens ist (dies schließt Whitespace und nicht druckbare Sonderzeichen aus). toString() Für Arrays liefert toString() allerdings nur den Klassennamen und den Hashcode. 45.ltrim(str). in der sie im Array gespeichert sind. schließlich in eckige Klammern gefasst und zurückgeliefert: int[] ints = { 1. wie in der Notimplementierung von Object festgelegt.

append(a[0]). nur dass der erzeugte String nicht in Klammern gefasst wird und der Programmierer selbst bestimmen kann. String separator) { if (a == null) return "null". i < a. double etc. Sollen Unterarrays ebenfalls durch Auflistung ihrer Elemente dargestellt werden. if (a.length == 0) return "". Ich stelle hier nur die Implementierungen für int[].length. Und auf die Klammerung der Array-Elemente könnte man gut verzichten. werden diese allerdings wiederum nur durch Klassenname und Hashcode repräsentiert (siehe oben). } return buf. durch welche Zeichen oder Zeichenfolge die einzelnen Array-Elemente getrennt werden sollen.toString() Die statische Methode MoreString.append(separator). durch welche Zeichenfolge die einzelnen Array-Elemente getrennt werden sollen (zweites Argument: separator). StringBuilder buf = new StringBuilder(). StringBuilder buf = new StringBuilder().) müssen nach dem gleichen Muster eigene überladene Versionen geschrieben werden: /** * int[]-Arrays in Strings umwandeln */ public static String toString(int[] a. char.toString(). Listing 42: Arrays in Strings verwandeln .112 >> Arrays in Strings umwandeln Strings Enthält das Array als Elemente weitere Arrays. wenn der Programmierer selbst bestimmen könnte. String separator) { if (a == null) return "null". buf. MoreString.und Object[]-Arrays vor. for (int i = 1. Schön wäre zum Beispiel. long. if (a.length == 0) return "".append(a[i]).toString(). Für Arrays mit Elementen anderer primitiver Datentypen (boolean.toString() ist weitgehend identisch zu Arrays. Auch wenn die Array-Methode toString() der Vorstellung einer praxisgerechten Array-toString-Methode schon sehr nahe kommt. } /** * Object[]-Arrays in Strings umwandeln */ public static String toString(Object[] a. rufen Sie die Arrays-Methode deepToString() auf. i++) { buf. lässt sie sich noch weiter verbessern. buf.

String intStr. die Elemente eines Arrays wahlweise durch Leerzeichen.toString()).append(separator).toString()). Mögliche Anwendungen wären zum Beispiel die Zerlegung eines Textes in ein Array von Wörtern oder die Extraktion von Zahlen aus einem String. for (int i = 1.toString(). 45. } Listing 42: Arrays in Strings verwandeln (Forts.) Mit Hilfe dieser Methoden ist es ein Leichtes.toString(ints.append(a[i]. in einen passenden Datentyp umzuwandeln und als Array zu verwalten. } return buf. ist es natürlich auch denkbar. "\n"). "\t"). ".toString(ints.length. . intsStr = MoreString. Array-Elemente in Strings zu verwandeln und zu einem einzigen String zusammenzufassen. -9. intsStr = MoreString.append(a[0]. -312. "). i < a. einen String in Teilstrings zu zerlegen. i++) { buf. Kommata. 7005}.>> Strings 113 Strings buf. 55.toString(ints. Semikolons oder auch Zeilenumbrüche getrennt in einen String zu verwandeln: int[] ints = { 1. Abbildung 22: Vergleich der verschiedenen Array-to-String-Methoden 36 Strings in Arrays umwandeln Ebenso wie es möglich ist. buf. intsStr = MoreString.

114 >> Strings in Arrays umwandeln Strings 1. den String in ein Array von Teilstrings zu zerlegen.println(" Array nach split(\"\\\\s+\") :\n"). zerlegt und in ein Array von int-Werten umgewandelt: public class Start { public static void main(String args[]) { String paul_ernst = "Die Narren reden am liebsten von der Weisheit. der durch Tabulatoren getrennte Zahlenwerte enthält. for (String s : words) System. Die Teilstrings werden als String-Array zurückgeliefert. Das Start-Programm zu diesem Rezept demonstriert die Umwandlung in Arrays an zwei Beispielen. " + "die Schurken von der Tugend. String[] buf = aString. an welchen Textstellen der String aufgebrochen werden soll. im zweiten Fall wird ein String. System. String data = "1\t-234\t5623\t-90". System.split("\\s+").out.split(" ").out. Im einfachsten Fall besteht dieser reguläre Ausdruck aus einem oder mehreren Zeichen respektive Zeichenfolgen.out.out. // String in Array verwandeln String[] words = paul_ernst. System.println("\t" + s).out.". Ansonsten: 2. // Leerzeichen als // Trennzeichen String[] substrings = aString. System. die als Trennzeichen zwischen den informationstragenden Teilstrings stehen. der angibt. // Whitespace als // Trennzeichen Wenn es darum ginge. Dies geschieht am effizientesten mit der split()-Methode (siehe Rezept 29).println(" \"" + paul_ernst + "\"\n\n"). Als Erstes zerlegen Sie den String in Teilstrings. Im ersten Fall wird ein Text in Wörter zerlegt (mit Whitespace als Trennzeichen). // Ausgabe der Array-Elemente System. Wandeln Sie das String-Array in ein Array von Elementen des gewünschten Zieltyps um.split("\\s+"). ist die Arbeit an diesem Punkt bereits getan (von einer eventuell erforderlichen Nachbearbeitung der Teilstrings einmal abgesehen).println("\n\n Text in Woerter-Array zerlegen:\n"). Als Argument übergeben Sie einen regulären Ausdruck (in Form eines Strings).out.println("\n\n String mit int-Daten in Zahlen zerlegen:\n"). // String in Array verwandeln Listing 43: Demo-Programm zur Umwandlung von Strings in Arrays .println(" \"" + data + "\"\n\n").

++i) numbers[i] = Integer. for (Integer i : numbers) System.println(" Fehler bei Umwandlung in Integer").parseInt(buf[i]).out. } // Ausgabe der Array-Elemente System. try { for (int i = 0. } } Listing 43: Demo-Programm zur Umwandlung von Strings in Arrays (Forts.) Abbildung 23: Umwandlung von Strings in Arrays .length.>> Strings 115 Strings String[] buf = data. i < numbers.split("\t").out. int[] numbers = new int[buf.println("\t" + i).println(" Array nach split(\"\\t\"):\n").length].err. } catch (NumberFormatException e) { System.

min) + min. i++) { int length. Das folgende Beispiel zeigt eine mögliche Implementierung zur Erzeugung von solchen Zufallsstrings mit einer wählbaren Mindest. int counter = 0. for(int i = 1. while(counter < length) { // Bereichsgrenzen: von A = 65 bis z = 122 // Bereich 91-96 sind Sonderzeichen -> überspringen int value = randGen.und Maximallänge: /** * Klasse zur Erzeugung zufälliger Zeichenketten aus dem Bereich a-Z */ import java. eine große Anzahl an unterschiedlichen. Listing 44: RandomStrings. die man zur Generierung von gleichverteilten Zufallszahlen verwenden kann. Für den Zufall sorgt dabei die Klasse java.116 >> Zufällige Strings erzeugen Strings 37 Zufällige Strings erzeugen Für Testzwecke ist es oft hilfreich. beispielsweise aus dem Bereich von 65 bis 122.util. class RandomStrings { /** * Erzeugt zufällige Zeichenketten aus dem Bereich a-Z * * @param num Anzahl zu generierender Zeichenketten * @param min minimale Länge pro Zeichenkette * @param max maximale Länge pro Zeichenkette * @return ArrayList<String> mit Zufallsstrings */ public static ArrayList<String> createRandomStrings(int num. int max) { Random randGen = new Random(System. Aus einem ASCII-Wert kann dann einfach per Cast das entsprechende Zeichen erzeugt werden.nextInt(max + 1 . Als Java-Programmierer haben Sie natürlich die besten Möglichkeiten. in dem die ASCII-Codes der Groß.nextInt(122 + 1 -65) + 65.*.und Kleinbuchstaben liegen. StringBuilder curStr = new StringBuilder(). i <= num. zufällig zusammengesetzten Zeichenketten zur Verfügung zu haben.java – ein String-Generator . int min. if(min > max || num <= 0) // nichts zu tun return result. // Länge des nächsten Strings zufällig wählen if(min == max) length = min.Random. else length = randGen.util. ArrayList<String> result = new ArrayList<String>(). solche Zufallsstrings zu erzeugen.currentTimeMillis()).

) Das Start-Programm zu diesem Rezept benutzt den String-Generator zur Erzeugung von zehn Strings mit vier bis fünfzehn Buchstaben.append(z).add(curStr.java – ein String-Generator (Forts.>> Strings 117 Strings if(value >= 91 && value <= 96) continue. for(String str : strings) System. public class Start { public static void main(String[] args) { // Zehn Strings mit vier bis fünfzehn Zeichen erzeugen ArrayList<String> strings. curStr.toString()). } result.out. 4. } } Listing 45: Erzeugung zufälliger Zeichenketten Abbildung 24: Zufällige Strings der Länge 4 bis 15 .createRandomStrings(10. 15). } return result.println(str). char z = (char) value. else counter++. strings = RandomStrings. } } Listing 44: RandomStrings.

num). Die Kombination zweier Klassen stellt alles Nötige bereit: Mit java.StringTokenizer wird der Text in die interessierenden Wörter zerlegt. * * @param text zu analysierender Text * @return HashMap<String. num = new Integer(numValue). d. lässt sich mit Java sehr elegant realisieren. Satzzeichen und gängige * Sonderzeichen werden ignoriert.h. } else { // Wort bereits vorhanden -> Zähler erhöhen int numValue = num.intValue() + 1.:?!(){}[]"). /** * Klasse zur Erstellung von Wortstatistiken */ import java. die als Trennzeichen interpretiert werden sollen. num). Integer num = wordTable. wordTable. Integer> countWords(String text) { StringTokenizer st = new StringTokenizer(text. Alle gefundenen Wörter werden in einer Hashtabelle (z. Bequemerweise kann man der Klasse auch einen String mit allen Zeichen mitgeben.*. wird lediglich der alte Zählerwert um eins erhöht.. class WordStatistics { /** * Erstellt eine Wortstatistik.java . if(num == null) { // bisher noch nicht vorhanden -> neu einfügen mit Zählwert = 1 num = new Integer(1).*. wordTable. Integer> mit Wörtern und ihren Häufigkeiten */ public static HashMap<String. Dadurch kann man beispielsweise neben dem üblichen Leerzeichen auch andere Satzzeichen beim Lesen überspringen. Wenn ein Wort bereits vorhanden ist.put(word. while(st. zusammen mit ihrer aktuellen Häufigkeit.util.118 >> Wortstatistik erstellen Strings 38 Wortstatistik erstellen Das Erstellen einer Wortstatistik.get(word).&%$§.put(word. import java. } Listing 46: WordStatistics.util. java.io. Integer>(). "\n\" -+.hasMoreTokens()) { String word = st.nextToken().HashMap) abgespeichert. Rückgabe ist eine Auflistung aller * vorkommenden Wörter und ihrer Häufigkeit. HashMap<String. Integer> wordTable = new HashMap<String.B. das Erkennen der Wörter inklusive ihrer Häufigkeit.util.

length != 1) { System. Set<String> wordSet = statistic.get(word). try { BufferedReader reader = new BufferedReader( new FileReader(args[0])). public class Start { public static void main(String[] args) { if(args.readLine()) != null) text. word. num).next(). statistic = WordStatistics. while(it. int num = statistic.) Das Start-Programm zu diesem Rezept demonstriert den Aufruf.out. } } Listing 46: WordStatistics.size()).keySet(). } catch(Exception e) { e.println("Aufruf: <Dateiname>").hasNext()) { String word = it.>> Strings 119 Strings } return wordTable.java (Forts. String line.printStackTrace(). Listing 47: Erstellen einer Wortstatistik } .intValue().exit(0).append(line + "\n"). System. } // Statistik erstellen HashMap<String. System. statistic.console(). // Statistik ausgeben System. } // Datei einlesen StringBuilder text = new StringBuilder().countWords(text.console().printf(" %s : %d \n". while((line = reader.toString()). Iterator<String> it = wordSet. Integer> statistic.iterator().printf("\n Anzahl unterschiedlicher Wörter: %d\n".

bietet StringTokenizer die Methode countTokens() an: StringTokenizer st = new StringTokenizer(meinText.) Abbildung 25: Ausgabe von Worthäufigkeiten Die Gesamtzahl an unterschiedlichen Wörtern kann man wie oben gezeigt über die Methode size() der Hashtabelle ermitteln.120 >> Wortstatistik erstellen Strings } } } Listing 47: Erstellen einer Wortstatistik (Forts.out.countTokens()).&%$§. H in we is .:?!(){}[]").println("Gesamtzahl Wörter: " + st. "\n\" -+. System.. Falls man allerdings die gesamte Anzahl an Wörtern (inklusive Wiederholungen) benötigt.

Wenn Sie explizit mit Datumswerten programmieren müssen.out. Das Calendar-Objekt repräsentiert die aktuelle Zeit (Datum und Uhrzeit) gemäß der auf dem System eingestellten Lokale und Zeitzone. verwenden Sie Calendar oder GregorianCalendar. die von getInstance() zurückgeliefert werden: sun. Die Date-Klasse arbeitet nämlich intern mit dem gregorianischen Kalender. Calendar calendar = Calendar. das aktuelle Datum abzufragen.println(today).getDateTimeInstance(). Leider gibt es derzeit nur drei vordefinierte Kalenderklassen.util.text. wenn Sie an der reinen Systemzeit interessiert sind oder die chronologische Reihenfolge verschiedener Zeiten prüfen wollen. Wenn Sie das Date-Objekt mit println() ausgeben (oder in einen String einbauen). JapaneseImperialCalendar (für ja_JP) und GregorianCalendar für alle anderen Lokalen. ein Objekt der Klasse Date zu erzeugen: import java. und speichert diese in dem Date-Objekt. Trotzdem sollten Sie den Empfehlungen von Sun folgen und die Klasse Date nur dann verwenden.util. besteht darin. Der Aufruf Calendar. // enthaltene Umlaute korrekt auszugeben (siehe Rezept 85) System. ermittelt er die aktuelle Systemzeit als Anzahl Millisekunden. Bereits im JDK 1.out. Date today = new Date().01. java. . System. Dieser ist zwar weit verbreitet und astronomisch korrekt. jedoch bei weitem nicht der einzige Kalender. vergangen sind.BuddhistCalendar für die Thai-Lokale (th_TH).getInstance().1 wurden der Klasse Date daher die abstrakte Klasse Calendar und die von Calendar abgeleitete Klasse GregorianCalendar an die Seite gestellt. die seit dem 01.printf("%s\n". Datum und Uhrzeit 39 Aktuelles Datum abfragen Ausgabe: Thu Mar 31 10:54:31 CEST 2005 Wenn Sie dem Konstruktor keine Argumente übergeben.getInstance() liefert ein Objekt einer Calendar-Klasse zurück (derzeit für nahezu alle Lokalen eine GregorianCalendar-Instanz.console() anstelle von System.1970 00:00:00 Uhr.getInstance() kann sich der Programmierer automatisch den passenden Kalender zur Lokale des aktuellen Systems zurückliefern lassen. siehe oben).Date.format(calendar. wird seine toString()-Methode aufgerufen. // verwende zur Ausgabe System. Die Idee dahinter: Neben GregorianCalendar können weitere Klasse für andere Kalender implementiert werden.Datum und Uhrzeit Der einfachste und schnellste Weg. Von der statischen Methode Calendar.console().Calendar. um evt. GMT. // Aktuelles Datum mit Calendar abfragen import java.util. die aus der Anzahl Millisekunden das Datum berechnet.getTime()) ).DateFormat. Und genau hier liegt das Problem der Date-Klasse.

11) HOUR_OF_DAY // Stunde (0 . int date. . Liefert das Datum als Millisekunden seit/bis zum 01. Zur Bezeichnung der Felder siehe get(). int value) void set(int year. int minute. Stunde . Monat (0-11) und Tag (1-31). beginnend mit 1 DAY_OF_WEEK // Tag in Woche (1 (SUNDAY) ..01.) eines Calendar-Objekts können mit Hilfe der get-/setMethoden der Klasse abgefragt bzw. Monat. beginnend mit 0 WEEK_OF_YEAR // Woche in Jahr. int minute) void set(int year. Optional können auch noch Stunde (0-23). int date. Setzt das Datum gemäß dem übergebenen Date-Objekt. int month. int month.1970 00:00:00 Uhr. gesetzt werden. von ihrem Gebrauch wird abgeraten. Setzt Jahr.01. beginnend mit JANUARY SECOND // Sekunde (0-59) WEEK_OF_MONTH // Woche in Monat.. int hourOfDay. int date) void set(int year. int second) void setTime(Date d) void setTimeInMillis(long millis) Liefert das Datum als Date-Objekt zurück. Setzt den angegebenen Feldwert.1970 00:00:00 Uhr. int month. beginnend mit 1 DST_OFFSET // Sommerzeitverschiebung in Millisekunden ERA // vor oder nach Christus HOUR // Stunde vor oder nach Mittag (0 . int hourOfDay.23) MILLISECOND // Millisekunden (0-999) MINUTE // Minuten (0-59) MONTH // Monat. Die Felder werden durch folgende Konstanten ausgewählt: AM_PM // AM (Vormittag) oder PM (Nachmittag) DATE // entspricht DAY_OF_MONTH DAY_OF_MONTH // Tag im Monat. Tabelle 15: Get-/Set-Methoden zum Abfragen und Setzen der Datumsfelder der CalendarKlasse A c h tun g Die Klasse Date enthält ebenfalls Methoden zum Abfragen und Setzen der einzelnen Datums. Methode Datum und Uhrzeit int get(int field) Beschreibung Zum Abfragen der verschiedenen Feldwerte.und Zeitfelder. Setzt das Datum gemäß der übergebenen Anzahl Millisekunden seit/bis zum 01. Diese sind jedoch als »deprecated« eingestuft.7 (SATURDAY)) DAY_OF_WEEK_IN_MONTH // 7-Tage-Abschnitt in Monat. beginnend mit 1 YEAR // Jahr ZONE_OFFSET // Verschiebung für Zeitzone in Millisekunden Date getTime() long getTimeInMillis() void set(int field. GMT zurück.122 >> Aktuelles Datum abfragen Die Felder (Jahr. GMT. beginnend mit 1 DAY_OF_YEAR // Tag im Jahr. Minute und Sekunde angegeben werden.

die vor der Einführung des gregorianischen Kalender liegen.. amerikanischen Geschichte. 0. liefert getInstance() eine Instanz von sun. birthday. 20).. Wenn Sie die Lebensdaten englischer bzw.util. ein Date-Objekt durch Angabe von Jahr (abzgl. September 1752. Monat und Tag: import java. ein Objekt für ein bestimmtes Datum zu erzeugen. Dies ist sinnvoll. Monat (0–11) und Tag (1–31) übergeben: import java.GregorianCalendar.. 20). 4. Datum und Uhrzeit Handelt es sich um ein Datum im gregorianischen Kalender. Oktober 1582 im julianischen Kalender entspricht – der gregorianische Kalender erstmals eingeführt wurde (in Spanien und Portugal). da an diesem Tag – der dem 5. rufen Sie setGregorianChange(Date(Long. Auf einem System. 4.BuddhistCalendar. Andere Kalender werden – mit Ausnahme des buddhistischen. rufen Sie setGregorianChange(Date(Long. amerikanischer Persönlichkeiten oder Daten aus der englischen bzw.MIN_VALUE)) auf. 0). 1. Wenn Sie den Kalender nicht vorgeben. Andere Länder folgten nach und nach.util. . . In England und Amerika begann der gregorianische Kalender beispielsweise mit dem 14. 8. Wenn Sie beliebige Daten nach dem julianischen Kalender berechnen wollen. lassen Sie sich von Calendar.util.Calendar. 4. Der Vollständigkeit halber sei erwähnt. 1900).MAX_VALUE)) auf. er ist als deprecated markiert. Calendar birthday = Calendar.getTime()). Vom Gebrauch dieses Konstruktors wird allerdings abgeraten. GregorianCalendar change = new GregorianCalendar(1752. sondern gemäß den Ländereinstellungen des aktuellen Systems auswählen möchten. Oktober 1582 berechnet die Klasse GregorianCalendar das Datum nach dem julianischen Kalender.>> Datum und Uhrzeit 123 40 Bestimmtes Datum erzeugen Es gibt verschiedene Wege. Calendar birthday = new GregorianCalendar(1964. 20).setGregorianChange(change. A c h t u ng Wenn Sie Interesse halber beliebige Daten nach dem gregorianischen Kalender berechnen möchten. das für die Thai-Lokale (th_TH) konfiguriert ist.. Monat (0-11) und Tag (1-31) zu erzeugen: Date birthday1 = new Date(64. ((GregorianCalendar) birthday). historisch korrekt darstellen möchten. müssen Sie das Datum der Einführung mit Hilfe der Methode setGregorianChange(Date) umstellen. des imperialistischen japanischen und des julianischen Kalenders (siehe unten) – derzeit nicht unterstützt. dass es auch die Möglichkeit gibt. Für Daten vor dem 15.getInstance(). für die Lokale ja_JP eine Instanz von JapaneseImperialCalendar und für alle anderen Lokalen eine Instanz von GregorianCalendar. können Sie direkt ein Objekt der Klasse GregorianCalendar erzeugen und dem Konstruktor Jahr. 14. .set(1964.getInstance() ein Objekt des lokalen Kalenders zurückliefern und ändern das von diesem Objekt repräsentierte Datum durch Setzen der Felder für Jahr.

er richtete sich nicht nach den Mondphasen./15. Land Spanien. Oktober der 15. wurde alle vier Jahre ein Schaltjahr eingelegt. der sich vom julianischen Kalender in der Berechnung der Schaltjahre unterscheidet. Februar/1.-Orthodoxe Kirche Türkei Einführung 04. Preußen England. Gleichzeitig wurde der gregorianische Kalender eingeführt. sondern nach der Länge des mittleren Sonnenjahres. Dezember 1582 05. im Jahr 1582. März fiel. 1752 17. Jahrhundert ein. Um die Differenz zum »angenommenen« Sonnenjahr auszugleichen. war ein reiner Sonnenkalender. die nicht durch 400 teilbar sind. März 1924 1927 Datum und Uhrzeit E x k ur s Tabelle 16: Einführung des gregorianischen Kalenders . Die meisten katholischen Länder folgten in den nächsten Jahren.2422 Tage lang (tropisches Jahr). korrigierte aber deren Längen auf die noch heute gültige Anzahl Tage. was dem tatsächlichen mittleren Wert von 365./14. verfügte Papst Gregor XIII. Januar/14. Der julianische Kalender übernahm die zwölf römischen Monate. auf den 5. in Kraft gesetzt. September 1612 02. Teile Italiens Frankreich Bayern Hzm. Oktober 1582 09. Während der julianische Kalender alle vier Jahre ein Schaltjahr einlegte. Der julianische Kalender hinkte seiner Zeit also immer weiter hinterher. Oktober 1583 22. dass in diesem Jahr auf den 4. galt in Europa der julianische Kalender.2425 Tage lang. Februar 1918 10./20. August/02. so dass das Jahr fortan 365 Tage enthielt. Oktober folgen sollte. Chr.) Spanien.124 >> Bestimmtes Datum erzeugen Der gregorianische Kalender und die Klasse GregorianCalendar Vor der Einführung des gregorianischen Kalenders im Jahre 1582 durch Papst Gregor XIII.2422 Tagen (tropisches Jahr) sehr nahe kommt. während die protestantischen Länder den Kalender aus Opposition zum Papst zunächst ablehnten. Durch diese verbesserte Schaltregel ist ein Jahr im gregorianischen Kalender durchschnittlich 365. sind im gregorianischen Kalender alle Jahrhundertjahre. statt den 21./16. von dem ägyptischen Astronomen Sosigenes ausgearbeitet und von Julius Cäsar im Jahre 46 v. Oktober ein. die Sosigenes zu 365. Portugal und Teile Italiens führten den gregorianischen Kalender wie vom Papst vorgesehen in der Nacht vom 4. März 1753 31./15. Tatsächlich ist das mittlere Sonnenjahr aber nur 365.h. d. Die orthodoxen Länder Osteuropas führten den gregorianischen Kalender gar erst im 20. bis im Jahre 1582 das Primar-Äquinoktium auf den 11. Um die Differenz auszugleichen. Der julianische Kalender. (Erst nach 3000 Jahren wird sich die Abweichung zu einem Tag addieren.25 Tagen berechnete.. Portugal. keine Schaltjahre./24. Amerika Schweden Russland Griech.

01.. Die Methode ist rein zum Debuggen gedacht und packt in den zurückgelieferten String alle verfügbaren Informationen über den aktuellen Zustand des Objekts. Uhrzeit.getTime().) Monatskürzel. GMT.) Wochentagskürzel. müssen Sie sich die im Calendar-Objekt gespeicherte Zeit als Date-Objekt zurückliefern lassen und dessen toString()-Methode aufrufen: Calendar calendar = Calendar. sieht sich allerdings getäuscht. Ist die Datums-/Zeitangabe in ein Date-Objekt verpackt. als Date-Objekt oder als Anzahl Millisekunden seit/ bis zum 01.1970 00:00:00 Uhr.getInstance(). festgelegt und umgekehrt auch als Werte der Datumsfelder. Das Datum der Einführung kann mit Hilfe der Methode setGregorianChange(Date d) angepasst werden. Anhand des Datums der Einführung des gregorianischen Kalenders interpretiert sie Datumswerte entweder als Daten im gregorianischen oder julianischen Kalender (siehe Hinweis weiter oben).>> Datum und Uhrzeit 125 Das Datum. 41 Datums-/Zeitangaben formatieren Zur Formatierung von Datums. Ausgabe: Thu Mar 31 10:54:31 CEST 2005 Datum und Uhrzeit Die Klasse GregorianCalendar implementiert eine Hybridform aus gregorianischem und julianischem Kalender.. Um dennoch einen vernünftigen Datums-/Zeit-String zu erhalten.out. Zeitzone und Jahr: Thu Mar 31 10:54:31 CEST 2005 Wer Gleiches von der toString()-Methode der Klasse Calendar erwartet. erhält man auf diese Weise einen String aus (engl. Für die korrekte Umrechnung zwischen Datumsfeldern und Anzahl Millisekunden sorgen dabei die von Calendar geerbten und in GregorianCalendar überschriebenen protectedMethoden computeFields() und computeTime(). Tag . kann durch Angabe der Datumsfelder (Jahr. Monat. Date-Objekt oder Anzahl Millisekunden abgefragt werden. Date today = calendar.und Zeitangaben gibt es drei Wege zunehmender Komplexität. System. (engl.). Tag im Monat.println(date). aber auch wachsender Gestaltungsfreiheit: toString() DateFormat-Stile SimpleDateFormat-Muster Formatierung mit toString() Die einfachste Form der Umwandlung einer Datums-/Zeitangabe in einen String bietet die toString()-Methode. das eine GregorianCalendar-Instanz repräsentiert. .

definiert aber verschiedene statische Factory-Methoden. Sie rufen die gewünschte Factory-Methode auf und lassen sich ein Formatierer-Objekt zurückliefern.SHORT). Uhrzeit oder der Kombination aus Datum und Uhrzeit zurückliefern. Die Klasse DateFormat definiert vier Factory-Methoden.126 >> Datums-/Zeitangaben formatieren Formatierung mit DateFormat-Stilen Die Klasse DateFormat definiert vier vordefinierte Stile zur Formatierung von Datum und Uhrzeit: SHORT. 2.format(calendar. März 2005 19:51 19:51:14 19:51:14 CEST 19. Locale loc) gibt eine Übersicht über die Formatierung durch die verschiedenen Stile.51 Uhr CEST Uhrzeit SHORT MEDIUM (= DEFAULT) LONG FULL Tabelle 17: DateFormat-Stile .03. Die Formatierung mit DateFormat besteht daher aus zwei Schritten: 1. März 2005 Donnerstag.getDateInstance(DateFormat. die passende Objekte abgeleiteter Klassen (derzeit nur SimpleDateFormat) zur Formatierung von Datum. Stil Datum SHORT MEDIUM (= DEFAULT) LONG FULL Formatierung (Lokale de_DE) 31. Sie übergeben die Datums-/Zeitangabe als Date-Objekt an die format()-Methode des Formatierers und erhalten den formatierten String zurück. LONG und FULL. so dass Sie einen anderen Stil bzw.03.getTime())). Calendar calendar = Calendar. MEDIUM (= DEFAULT).text. Stil und Lokale vorgeben können.05 31. Datum und Uhrzeit Die Klasse DateFormat selbst ist abstrakt. import java. die gemäß der auf dem System eingestellten Lokale formatieren: getInstance() getDateInstance() getTimeInstance() getDateTimeInstance() // // // // // Formatierer für Formatierer für Formatierer für Formatierer für im DEFAULT-Stil Datum und Uhrzeit im SHORT-Stil Datum im DEFAULT-Stil (= MEDIUM) Uhrzeit im DEFAULT-Stil (= MEDIUM) Datum und Uhrzeit (= MEDIUM) Die letzten drei Methoden sind zweifach überladen. // Formatierer für reine Datumsangaben im SHORT-Stil DateFormat df = DateFormat. 31. String str = df. beispielsweise: getDateInstance(int stil) getDateInstance(int stil.2005 31.getInstance().DateFormat.

einstellig (soweit möglich) bzw. »So« (für englischsprachige Lokale werden dreibuchstabige Kürzel verwendet) Wochentag (ausgeschrieben) »AM« oder »PM« Stunde (0-23). 01 . einstellig (soweit möglich) bzw. ww W D.. DDD d. 02 . immer zweistellig (01. einstellig (soweit möglich) bzw.) Stunde (1-12). SimpleDateFormat besitzt einen Konstruktor SimpleDateFormat(String format.und zeitrelevante Formatanweisungen und darf beliebig durch weitere Zeichenfolgen. einstellig (soweit möglich) bzw.. HH h. 02 . hh K. kk Tabelle 18: SimpleDateFormat-Formatanweisungen . immer zweistellig (01. »Fr«. immer zweistellig (00. 02 . SimpleDateFormat df = new SimpleDateFormat("'Heute ist der 'dd'. Dieser String enthält feste datums. immer zweistellig (00. Die wichtigsten Formatanweisungen für Datum und Zeit lauten: Format G Beschreibung »v. zweistellig Jahr. DD. Juni". einstellig (soweit möglich) bzw. kann sich mit Hilfe der abgeleiteten Klasse SimpleDateFormat einen individuellen Stil definieren..) Wochentag-Kürzel: »Mo«. Dieser Aufruf erzeugt eine Ausgabe der Art: "Heute ist der 12... »Di«. immer zweistellig (00. »Mi«..« oder »n. 01 . »Sa«.. MM MMM MMMM w. KK k.. Chr. 01 .>> Datum und Uhrzeit 127 H i nwe i s Zur landesspezifischen Formatierung mit Lokalen siehe auch Rezepte in Kategorie »Internationalisierung«. Locale loc) der neben der Angabe der Lokale auch einen Formatstring erwartet. einstellig (soweit möglich) bzw...) Woche im Monat Tag im Jahr. Datum und Uhrzeit Formatierung mit SimpleDateFormat-Mustern Wer mit den vordefinierten DateFormat-Formatstilen nicht zufrieden ist. Chr« (in englischsprachigen Lokalen »BC« oder »AD«) Jahr..) Stunde (1-24). immer zweistellig (00.) Monat als 3-Buchstaben-Kurzform voller Monatsname Woche im Jahr.) yy yyyy M. unterbrochen sein.) Tag im Monat.) Stunde (1-12).. einstellig bzw. dd E EEEE a H. 'MMMM"). immer zweistellig (01. vierstellig Monat. die in ' ' eingeschlossen sind.. 002 . zweistellig (soweit möglich) oder immer dreistellig (001. »Do«. einstellig (soweit möglich) bzw. 01 ....

) Sekunden.. von welcher Klasse Sie sich die Namen der Wochentage und Monate zurückliefern lassen. Die Strings mit den Namen der Wochentage oder Monate können Sie selbst aufsetzen … oder sich von einer passenden Java-Klasse zurückliefern lassen.) 42 Wochentage oder Monatsnamen auflisten Manchmal ist es nötig. mm s. einstellig (soweit möglich) bzw. n). 002 .oder Zeitangaben in lokalisierter Form zurückliefert. . dann rufen Sie eine der Methoden auf und erhalten ein String-Array mit den Namen der Wochentage von Sonntag bis Samstag bzw. ss Beschreibung Minuten.128 >> Wochentage oder Monatsnamen auflisten Format m. die alle wichtigen Bestandteile von Datums. der Monatsnamen von Januar bis Dezember zurück.oder Wochentagsnamen anlegen Der Einsatz der Methoden ist denkbar einfach. immer zweistellig (00. einstellig bzw. immer zweistellig (00.. die Namen der Wochentage oder Monate aufzulisten – beispielsweise um sie über ein Listenfeld zur Auswahl anzubieten.und Monatsnamen gibt es hingegen kaum etwas Besseres als die DateFormatSymbols-Methoden getWeekdays() und getMonths(): String[] String[] String[] String[] getWeekdays() getShortWeekdays() getMonths() getShortMonths() // // // // liefert liefert liefert liefert die die die die Wochentagsnamen Kurzformen der Wochentagsnamen Monatsnamen Kurzformen der Monatsnamen String-Arrays der Monats. (Für diese Aufgabe bedient man sich besser eines DateFormat-Objekts. String[] weekdayNames = dfs.printf("\t%s%n".. Im Paket java.) Millisekunden.) Zeitzone Zeitzone (gemäß RFC 822) Datum und Uhrzeit S. 01 . zweistellig (soweit möglich) oder immer dreistellig (001.getWeekdays(). Bleibt noch zu klären. for(String n : weekdayNames) System. einstellig (soweit möglich) bzw... Zuerst erzeugen Sie ein DateFormatSymbolsObjekt. doch gilt dies vornehmlich für die Formatierung von Datums.. sie in eine Tabelle einzubauen oder Ähnliches. Uhrzeitstrings. SS.bzw.) Für die Abfrage der lokalisierten Wochentags. // Wochentage in der Sprache des Systems DateFormatSymbols dfs = new DateFormatSymbols().text gibt es eine Klasse namens DateFormatSymbols. siehe Rezept 41.console(). 01 . Letztere Vorgehensweise produziert in der Regel weniger Code und gestattet Ihnen zudem die Wochentage und Monate in der Sprache des aktuellen Systems anzuzeigen. welches dann intern mit DateFormatSymbols arbeitet. Zwar empfiehlt die API-Dokumentation diese Klasse nicht direkt zu verwenden. SSS z Z Tabelle 18: SimpleDateFormat-Formatanweisungen (Forts.

getDisplayName(Calendar. legen Sie bei der Instanzierung des DateFormatSymbols-Objekts fest. Listenfelder mit Monats. // Monate in französisch DateFormatSymbols dfs = new DateFormatSymbols(new Locale("fr". siehe Rezept 215. JList weekdayList = new JList(dfs. wird die Sprache des aktuellen Systems verwendet.>> Datum und Uhrzeit 129 Die Sprache. Von der Schwestermethode getDisplayNames() können Sie sich eine Map<String. "NO")). eignet sie sich nicht zum Aufbau von Listenfeldern oder ähnlichen geordneten Auflistungen der Wochentage oder Monate. wird die Sprache dieser Lokale verwendet. Sie hätten sie zuvor umgestellt.oder Wochentagsnamen zurückliefern lassen. Calendar. Listing 48: Listenfeld mit Wochentagsnamen Um exakt zu sein: Es wird die Sprache der Standardlokale verwendet. Datum und Uhrzeit H in we is } }). "FR")). Wochentag: " + s).1 Wenn Sie dem Konstruktor eine Lokale übergeben. } // Listenfeld mit Bildlaufleiste ausstatten JScrollPane sp1 = new JScrollPane(weekdayList).getMonths().getSource(). Wenn Sie zu einem bestimmten Datum den lokalisierten Monats.MONTH.LONG.DAY_OF_WEEK.oder Wochentagsnamen Der Code zum Aufbau eines Listenfelds mit Wochentagsnamen könnte wie folgt aussehen: // Listenfeld anlegen und mit Wochentagsnamen füllen DateFormatSymbols dfs = new DateFormatSymbols(). 1.getDisplayName(Calendar.getSelectedValue(). // Listenfeld konfigurieren weekdayList. Wenn Sie den Konstruktor wie oben ohne Argument aufrufen. Calendar. es sei denn. new Locale("no". .addListSelectionListener(new ListSelectionListener() { public void valueChanged(ListSelectionEvent e) { JList list = (JList) e.LONG. Die Standardlokale spiegelt allerdings die Systemkonfiguration wider. in der die Namen zurückgeliefert werden.SINGLE_SELECTION). String[] monthNames = dfs. String s = (String) list. Allerdings verwendet diese Collection die Namen als Schlüssel und da sie zudem ungeordnet ist.getDefault()). können Sie seit Java 6 dazu die Calendar-Methode getDisplayName() verwenden: String weekdayName = calendar. weekdayList.setSelectionMode(ListSelectionModel. Locale. if (s != null) { lb3. String monthName = calendar.getWeekdays()). Integer>-Collection der Monats.setText("Ausgew.oder Wochentagsnamen abfragen möchten.

out. liefert null zurück. welche ab der Position pos sucht. DateFormat parser = DateFormat. liefern sie das Datum als Date-Objekt zurück.GERMANY).console().err.println(" Kein gueltiges Datum (TT. Der folgende Code liest deutsche Datumseingaben im MEDIUM-Format (TT.printf("\n %s \n". Datum und Uhrzeit Listing 48: Listenfeld mit Wochentagsnamen (Forts.GERMANY).format(date)). Locale.MEDIUM. } Eine Beschreibung der vordefinierten DateFormat-Formate sowie der Definition eigener Formate mit SimpleDateFormat finden Sie in Rezept 41.MM. Sie müssen nur zum Füllen des Listenfelds die DateFormatSymbols-Methode getMonths() aufrufen. 43 Datumseingaben einlesen und auf Gültigkeit prüfen Zum Einlesen von Datumseingaben benutzt man am besten eine der parse()-Methoden von DateFormat: Date parse(String source) Date parse(String source. ob er ein Datum enthält. ParsePosition pos) So wie die format()-Methode von DateFormat ein Date-Objekt anhand der eingestellten Lokale und dem ausgewählten Pattern in einen String formatiert. // JScrollPane mit Listenfeld in Formular einfügen getContentPane(). löst die erste Version eine ParseException aus.FULL. Die zweite Version.getDateInstance(DateFormat. .console() anstelle von // System.und Ausgabe werden daher unterschiedliche DateFormat-Instanzen (parser und formatter) erzeugt: Date date = null.JJJJ) ein und gibt sie zur Kontrolle im FULL-Format aus. } catch(ParseException e) { System. um evt. formatter. 200. das Lokale und Muster entspricht. 40. Wenn ja. 100)). Ein Beispiel für das Einlesen von Datumswerten mit eigenen SimpleDateFormaten finden Sie im Start-Programm zu diesem Rezept.parse(args[0]).JJJJ)").130 >> Datumseingaben einlesen und auf Gültigkeit prüfen sp1.add(sp1).) Listenfelder mit den Monatsnamen können Sie analog erzeugen.MM.getDateInstance(DateFormat. enthaltene Umlaute korrekt auszugeben // (siehe Rezept 85) System. Für Ein. // Datum auf Konsole ausgeben (verwendet System. analysieren die parse()-Methoden einen gegebenen String. Enthält der übergebene String keine passende Datumsangabe. try { // Datum aus Kommandozeile einlesen date = parser. DateFormat formatter = DateFormat.setBounds(new Rectangle(30. Locale.

Der vierte Aufruf zeigt.out. Jahrhundert verlegt.println("\t t1 liegt nach t2").after(t2) ) System. Die Eingabe 05.25 bereits als 6.h. er liest die Anzahl der Monate – egal aus wie vielen Ziffern die Angabe besteht. stehen Ihnen neben compareTo() die speziellen Methoden before() und after() zur Verfügung: // gegeben Calendar t1 und t2 if (t1. April 1925 interpretiert wird.04. Jahresangaben aus einem oder mehr als zwei Buchstaben (»y«.oder Calendar-Objekt) gleich sind. während die Eingabe 06. Zweistellige Jahresangaben werden beim Parsen als Kürzel für vierstellige Jahresangaben angesehen und so ergänzt. wie zu große Werte in die nächsthöhere Einheit umgerechnet werden. Wenn Sie dieses Verhalten unterbinden wollen. April 2024 geparst. Auch die Eingabe 05. ob zwei Datumswerte (gegeben als Date.04.) A c h tun g .println("gleiche Datumswerte"). Der überzählige Betrag wird in die nächsthöhere Einheit.equals(t2)) System. Um zu prüfen.24 wird dann als 5. für Monate also Jahre. brauchen Sie nur die Methode equals() aufzurufen: // gegeben Date t1 und t2 if (t1. wie oft ein bedeutungstragender Buchstabe. ob ein Datum zeitlich vor oder nach einem zweiten Datum liegt. April 2005 ausgeführt.04. Angenommen. »yyyy«) werden immer unverändert übernommen. dass das sich ergebende Datum nicht mehr als 80 Jahre vor und nicht weiter als 20 Jahre hinter dem aktuellen Datum liegt. etwa das M für Monatsangaben. umgerechnet.MEDIUM-Format für die deutsche Lokale. wie Datumsangaben im angelsächsischen Format abgewiesen werden.>> Datum und Uhrzeit 131 Eine Ausnahme bilden die Jahresangaben. 44 Datumswerte vergleichen Um festzustellen. Der dritte Aufruf demonstriert.out. d. rufen Sie setLenient(false) auf. (Tatsächlich können sogar Werte wie 35 oder 123 übergeben werden. Während »MM« bei der Formatierung eine zweistellige Ausgabe erzwingt. wiederholt wird.25 wird noch ins 21. Abbildung 26: Einlesen von Datumseingaben im DateFormat. sind für den Parser »M« und »MM« gleich. Datum und Uhrzeit Beim Parsen spielt es grundsätzlich keine Rolle.. das Programm wird am 05.

Datum und Uhrzeit int compareTo(Date/Object) boolean before(Date/Object) boolean after(Date/Object) Tabelle 19: Vergleichsmethoden für Datumswerte Vergleiche unter Ausschluss der Uhrzeit Die vordefinierten Vergleichsmethoden der Klasse Date und Calendar basieren allesamt auf der Anzahl Millisekunden seit dem 01. bedarf es demnach eigener Hilfsmethoden: /** * Prüft. Liefert true. wenn beide Objekte vom Typ Date sind und auf derselben Anzahl Millisekunden basieren. wenn der aktuelle Datumswert zeitlich vor dem übergebenen Datum liegt. ob zwei Calendar-Objekte den gleichen Tag im Kalender bezeichnen */ public static boolean equalDays(Calendar t1.equals(t2)). wenn der aktuelle Datumswert zeitlich nach dem übergebenen Datum liegt. Wenn ja. 3.MONTH) == t2.get(Calendar.get(Calendar. t1. GMT.YEAR)) && (t1. 0). // -1 Um Datumswerte ohne Berücksichtigung der Uhrzeit vergleichen zu können.get(Calendar. ob Jahr.get(Calendar. wenn beide Objekte vom Typ Calendar sind. /** * Prüft. ob der aktuelle Datumswert kleiner. Monat und Tag der beiden CalendarObjekte übereinstimmen. je nachdem. 5. Sie sind also nicht geeignet. System.YEAR) == t2.out. ob zwei Calendar-Objekte den gleichen Tag bezeichnen */ public static int compareDays(Calendar t1. Liefert true.01. wenn Sie feststellen möchten. 13 Uhr Calendar t2 = Calendar.getInstance(). Liefert -1. gleich oder größer dem übergebenen Wert ist.get(Calendar.132 >> Datumswerte vergleichen Date-/Calendar-Methode boolean equals(Object) Beschreibung Liefert true. April 2005. wenn der aktuelle Datumswert und das übergebene Datum identisch sind. Calendar t2) { return (t1. 12.get(Calendar.compareTo(t2)).DAY_OF_MONTH) == t2. Bei Calendar ist dies der Fall. April 2005.DAY_OF_MONTH)).MONTH)) && (t1.println("Vergleich mit equals() : " + t1. 0. 0 oder 1 zurück. // Datumsobjekt für den 5. ob zwei Datumswerte denselben Tag (ohne Berücksichtigung der Uhrzeit) bezeichnen: // Datumsobjekt für den 5.println("Vergleich mit compareTo(): " + t1. auf derselben Anzahl Millisekunden basieren und bestimmte Calendar-Charakteristika sowie die Zeitzone übereinstimmen.getInstance(). liefert sie true zurück.out.set(2005.1970 00:00:00 Uhr. Bei Date ist dies der Fall. // false System. 12 Uhr Calendar t1 = Calendar. Calendar t2) { . } Die Methode equalDays() prüft paarweise.

Für bestehende Calendar-Objekte können Sie die Uhrzeitfelder mit Hilfe von set() oder clear() auf 0 setzen: // Aktuelles Datum Calendar today = Calendar. Calendar clone2 = (Calendar) t2. genügt es. Monat und Tag angeben. clone1.get(Calendar.get(Calendar. erhalten Sie die Anzahl Millisekunden von der Methode getTimeInMillis().YEAR). clone2. Wenn Sie Datumsdifferenzen ohne Berücksichtigung der Uhrzeit berechnen wollen.clear(Calendar. t2. 1. vergangen sind.>> Datum und Uhrzeit Calendar clone1 = (Calendar) t1.getInstance(). ob sie nun durch ein Objekt der Klasse Date oder Calendar repräsentiert werden. 5.01.get(Calendar. schließen immer auch eine Uhrzeit ein.compareTo(clone2).set(t1. müssen Sie die Uhrzeit für alle Datumswerte auf einen gemeinsamen Wert setzen. t1. } 133 Die Methode compareDays() nutzt einen anderen Ansatz als equalDays(). die seit dem 01.compareTo() und liefert das Ergebnis zurück.getTimeInMillis() ). t2.MILLISECOND). Uhrzeit ausschalten Datumswerte.MONTH).1970 00:00:00 Uhr. 45 Differenz zwischen zwei Datumswerten berechnen Wie Sie die Differenz zwischen zwei Datumswerten berechnen. 0.MONTH).date2.DATE). Datum und Uhrzeit . Minuten. Dann vergleicht sie die Klone mit Calendar. werden die Uhrzeitfelder automatisch auf 0 gesetzt. Sie legt Kopien der übergebenen Calendar-Objekte an und setzt für diese die Werte der Stunden. sich die Datumswerte als Anzahl Millisekunden. return clone1. 0.DATE).YEAR).clear(Calendar. 0. Wenn Sie ein neues GregorianCalendar-Objekt für ein bestimmtes Datum setzen und nur die Daten für Jahr. t1. 0. ein Maß für den zeitlichen Abstand zwischen zwei Datumswerten zu erhalten. Minuten und Sekunden aber auch explizit auf 0 setzen: GregorianCalendar date1 = new GregorianCalendar(2002. zurückliefern zu lassen und voneinander zu subtrahieren: long diff = Math. hängt vor allem davon ab. GMT. clone2. 0). 1). Geht es lediglich darum. können Sie die Felder für Stunden.set(t2. Um Ihre Intention deutlicher im Quelltext widerzuspiegeln.get(Calendar.get(Calendar. GregorianCalendar date2 = new GregorianCalendar(2002.abs( date1. 0. clone1. wozu Sie die Differenz benötigen und was Sie daraus ablesen wollen. 5. Sekunden (set()-Aufruf) sowie Millisekunden (clear()-Aufruf) auf 0. 0. Für Date-Objekte rufen Sie stattdessen getTime() auf. 0).MILLISECOND).getTimeInMillis() . H i n we i s Wenn Sie mit Calendar-Objekten arbeiten.get(Calendar. 0).

46 Differenz zwischen zwei Datumswerten in Jahren. können Sie auf zweierlei Weise abfragen: Mit Hilfe der get-Methoden (getYears(). Tagen und Stunden berechnen // Stunden. Minuten und Sekunden übergeben: TimeSpan ts = new TimeSpan(0. 26).YEAR). In Deutschland beginnt die Sommerzeit am 27.get(Calendar. Tagen. 0). 1. 1 Monat = 30 Tage oder auch 1 Jahr = 365.get(Calendar. führt die Sommerzeitverschiebung zu eventuell unerwünschten Differenzberechnungen. true). 27). Um zwei Uhr nachts wird die Uhr um 1 Stunde vorgestellt. TimeSpan-Objekte können auf zweierlei Weise erzeugt werden: indem Sie den public-Konstruktor aufrufen und die Differenz selbst als Kombination aus Jahren.MONTH).25 Tage.) lassen Sie sich die Werte der zugehörigen Felder zurückliefern und erhalten so die Kombination aus Jahren. ob bei der Berechnung der Differenz auf Sommerzeit und Schaltjahre zu achten ist: GregorianCalendar time1 = new GregorianCalendar(2005. anzugeben. Wenn Sie eine Differenz in Jahren und/oder Monaten ausdrücken möchten.134 >> Differenz zwischen zwei Datumswerten in Jahren.get(Calendar. 2.set(today. Die Folge: Zwischen dem 27. Datum und Uhrzeit Eine Beschreibung der verschiedenen Datums. Stunden.getInstance(time1. März. true. Tagen und Stunden berechnen Weit komplizierter ist es. sowie boolesche Werte. getDays() etc.DATE). Tage. 2. Stunden. ob Sie mit festen Längen rechnen wollen (1 Jahr = 365 Tage. 2. März 00:00 Uhr liegen tatsächlich nur 23 Stunden. Die Klasse TimeSpan dient sowohl der Repräsentation als auch der Berechnung von Datumsdifferenzen. TimeSpan ts = TimeSpan.clear(Calendar. die Differenz zwischen zwei Datumswerten aufgeschlüsselt in Jahre. 0. indem Sie die Methode getInstance() aufrufen und dieser zwei GregorianCalendar-Objekte übergeben. von denen einer innerhalb und der andere außerhalb der Sommerzeit liegt. Daran sind vor allem zwei Umstände Schuld: Die Sommerzeit. Tagen. Wenn zwei Datumswerte verglichen werden. GregorianCalendar time2 = new GregorianCalendar(2005. In ihren private-Feldern speichert sie die Differenz zwischen zwei Datumswerten sowohl in Sekunden (diff) als auch ausgedrückt als Kombination aus Jahren. today. stehen Sie vor der Entscheidung. Tagen. Minuten und Sekunden. Stunden etc. // Millisekunden auf 0 setzen today. die der Methode mitteilen. 0. 0). März 00:00 Uhr und dem 28. Die Differenz. Minuten und Sekunden auf 0 setzen today. 1 Monat = 30.MILLISECOND). Stun- .4 Tage) oder ob Sie die exakten Längen berücksichtigen.und Uhrzeitfelder finden Sie in Tabelle 15 aus Rezept 40. 0. Trotzdem entspricht dies kalendarisch einem vollen Tag! Wie also sollte ein Programm diese Differenz anzeigen: als 23 h oder als 1 d? Die unterschiedlichen Längen der Monate und Jahre. die ein TimeSpan-Objekt repräsentiert. time2. today.

util.GregorianCalendar.TimeZone. (Die Methode toString() liefert auf diese Weise die Differenz als String zurück – nur dass sie natürlich direkt auf die Feldwerte zugreift. int minutes.hours = hours. } // Erzeugt aus zwei GregorianCalendar-Objekten // ein TimeSpan-Objekt Listing 49: Die Klasse TimeSpan . this. int hours. /** * Klasse zur Repräsentation und Berechnung von Zeitabständen * zwischen zwei Datumsangaben */ public class TimeSpan { private int years.days = days.hours = hours.years = years. // public Konstruktor public TimeSpan(int years. this. this. private long diff. private int days. int days. private int seconds. this. import java.Calendar. this. inWeeks() etc. Der Quelltext der Klasse TimeSpan sieht folgendermaßen aus: import java.seconds = seconds. long diff) { this.) können Sie sich die Differenz ausgedrückt in ganzzahligen Werten einer einzelnen Einheit (also beispielsweise in Jahren oder Tagen) zurückliefern lassen. Minuten und Sekunden. this.>> Datum und Uhrzeit 135 den. int hours. aus denen sich die Differenz zusammensetzt.util.util. diff = seconds + 60*minutes + 60*60*hours + 24*60*60*days + 365*24*60*60*years. this. this.days = days.) Datum und Uhrzeit Mittels der in-Methoden (inYears(). int seconds) { this.minutes = minutes. private int hours. int minutes.minutes = minutes.seconds = seconds.years = years. int seconds.diff = diff. import java. this. } // protected Konstruktor. private int minutes. wird von getInstance() verwendet protected TimeSpan(int years. int days.

Stunden. Am Beispiel der Berechnung des Sekundenanteils möchte ich dies kurz erläutern: Nachdem die Methode die Differenz durch 1000 dividiert hat.append("Kein Zeitunterschied"). s.append(minutes + " min "). } 60).append(years + " j "). s.) Am interessantesten ist zweifelsohne die Methode getInstance(). s. boolean leap) { // siehe unten } public public public public public public public public public public public int int int int int int int int int int int getYears() getDays() getHours() getMinutes() getSeconds() inYears() inWeeks() inDays() inHours() inMinutes() inSeconds() { { { { { return return return return return years.toString(). Rechnet man diff%60 (Modulo = Rest der Division durch 60). if (s. . } seconds. } (60 * 60 * 24)). diff = (last. } hours. Tage und Jahr zerlegt. } (diff / (diff / (diff / (diff / (diff / (diff).append(days + " t ").equals("") ) s. (60 * 60 * 24 * 365)). } days. indem Sie die Differenz aus den MillisekundenWerten (zurückgeliefert von getTimeInMillis()) berechnet.append(seconds + " sec "). boolean summer. } } Listing 49: Die Klasse TimeSpan (Forts.first.136 >> Differenz zwischen zwei Datumswerten in Jahren.append(hours + " std ").toString().getTimeInMillis())/1000. GregorianCalendar t2. } } { { { { { { return return return return return return (int) (int) (int) (int) (int) (int) public String toString() { StringBuilder s = new StringBuilder(""). } (60 * 60 * 24 * 7)). } (60 * 60)). diesen Wert durch Division mit 1000 in Sekunden umrechnet und dann durch sukzessive Modulo-Berechnung und Division in Sekunden. if(years > 0) if(days > 0) if(hours > 0) if(minutes > 0) if(seconds > 0) s. erhält man den Sekundenanteil: int seconds = (int) (diff%60). Minuten. } minutes. speichert sie den sich ergebenden Wert in der lokalen long-Variable diff. Tagen und Stunden berechnen Datum und Uhrzeit public static TimeSpan getInstance(GregorianCalendar t1. return s. die somit anfangs die Differenz in Sekunden enthält. s.getTimeInMillis() . die die Differenz zwischen zwei GregorianCalendar-Objekten berechnet.

diff /= 60.inDaylightTime(first.inDaylightTime(last.getTimeInMillis() > t2.first. // Sekunden. Datum und Uhrzeit // Jahre und Tage berechnen int days = 0. first = t2.getTime())) ) diff -= tz.getTime())) && (tz. if (leap) { // Schaltjahre ausgleichen int startYear = 0. int minutes = (int) (diff%60). // abzuziehende Schalttage // (da in Jahren enthalten) Listing 50: Quelltext der Methode TimeSpan. long diff. Minuten und Stunden berechnen int seconds = (int) (diff%60). } else { last = t2. last.getTimeInMillis())/1000. boolean leap) { GregorianCalendar first.getTime())) ) diff += tz.getDSTSavings()/1000.getDSTSavings()/1000.getTime())) && !(tz. // Schalttage in Zeitraum int subtractLeapDays = 0.getTimeInMillis() .inDaylightTime(first. } save = diff. first = t1. diff /= 60.>> Datum und Uhrzeit 137 Anschließend wird diff durch 60 dividiert und das Ergebnis zurück in diff gespeichert.getTimeZone(). TimeZone tz. Jetzt speichert diff die Differenz in ganzen Minuten (ohne den Rest Sekunden). diff /= 24. if( !(tz. diff /= 60.getTimeInMillis()) { last = t1. } // Differenz in Sekunden diff = (last. public static TimeSpan getInstance(GregorianCalendar t1. // Immer frühere Zeit von späteren Zeit abziehen if (t1. endYear = 0. int hours = (int) (diff%24). GregorianCalendar t2. boolean summer.getInstance() . if (summer) { // Sommerzeit ausgleichen tz = first. int years = 0. save.inDaylightTime(last. if( (tz. int leapDays = 0.

Dazu lässt sie sich von der Calendar-Methode getTimeZone() ein TimeZone-Objekt zurückliefern. das die Zeitzone des Kalenders präsentiert. i <= endYear.get(Calendar. for(int i = startYear.DAY_OF_MONTH) == 29))) endYear = last.DAY_OF_MONTH) < 29))) startYear = first.MONTH) == 1) && (first. // Jahre berechnen years = (int) ((diff-leapDays)/365).getInstance() (Forts.138 >> Differenz zwischen zwei Datumswerten in Jahren. seconds.get(Calendar. // Tage berechnen days = (int) (diff . } else { days years } = (int) (diff%365).YEAR). days. ist die Berücksichtigung von Sommerzeit und Schaltjahren.YEAR)-1.getTime())) ) .YEAR)+1. minutes. if (subtractLeapDays > leapDays) subtractLeapDays = leapDays.MONTH) == 1) && (last. Wird für den Parameter summer der Wert true übergeben.getTimeZone().get(Calendar.YEAR). hours. if( !(tz.getTime())) && (tz.get(Calendar. ob einer der Datumswerte (aber nicht beide) in die Sommerzeit der aktuellen Zeitzone fallen. ++i) if (first. if( (last. = (int) (diff/365). } Listing 50: Quelltext der Methode TimeSpan.((years*365) + subtractLeapDays)).isLeapYear(i)) ++leapDays.MONTH) < 1) || ( (first. Tagen und Stunden berechnen Datum und Uhrzeit if( (first.get(Calendar.MONTH) > 1) || ( (last.inDaylightTime(last. // in Jahren enthaltene Schalttage subtractLeapDays = (years+3)/4.get(Calendar. und übergibt nacheinander dessen inDaylightTime()-Methode die zu kontrollierenden Datumswerte: if (summer) { // Sommerzeit ausgleichen tz = first. else startYear = first.inDaylightTime(first.get(Calendar. prüft die Methode.get(Calendar.) Was zum Verständnis der getInstance()-Methode noch fehlt. return new TimeSpan(years. else endYear = last. (int) save).get(Calendar.get(Calendar.

geteilt durch 1000.inDaylightTime(first.getDSTSavings()/1000.GERMANY). März 00:00 Uhr und dem 28.02. Das Start-Programm zu diesem Rezept liest über die Befehlszeile zwei deutsche Datumsangaben im Format TT.ParseException. if( (tz. Wird für den Parameter leap der Wert true übergeben.2004 und dem 28. GregorianCalendar time2 = new GregorianCalendar().GregorianCalendar. Schaltjahre.getTime())) && !(tz.MM.02. java. gleicht die Methode die Sommerzeitverschiebung aus. wird das Jahr immer als 365 Tage aufgefasst. die in der Differenz komplett enthalten sind.text. TimeSpan ts. 139 Fällt tatsächlich einer der Datumswerte in die Sommerzeit und der andere nicht. System.JJJJ>"). auf diff hinzuaddiert oder von diff abzieht. sie zeitigt aber auch Merkwürdigkeiten.out.println(). berücksichtigt die Methode in Zeitdifferenzen.util. System.2005 zum anderen beide zu 1 Jahr berechnet.exit(0). GregorianCalendar time1 = new GregorianCalendar(). Schalttage.length != 2) { System. die sich über mehrere Jahre erstrecken. } try { Listing 51: Differenz zwischen zwei Datumswerten berechnen Datum und Uhrzeit } .JJJJ ein und berechnet die Differenz unter Berücksichtigung von Sommerzeit und Schaltjahren. if (args.02.03.text.2004 und dem 01. In den meisten Fällen führt diese Berechnung zu Ergebnissen. java.println(" Aufruf: Start <Datum: TT.getTime())) ) diff -= tz.MM.getDateInstance(DateFormat.MEDIUM.>> Datum und Uhrzeit diff += tz.Locale. die man erwartet. So wird die Differenz zwischen 01.2005 zum einen 29.getDSTSavings()/1000. java. Locale. indem sie sich von der getDSTSavings()-Methode des TimeZone-Objekts die Verschiebung in Millisekunden zurückliefern lässt und diesen Wert.02.util.2004 zu 29 Tagen berechnet und die Differenz zwischen dem 01.MM. März 00:00 Uhr wird dann beispielsweise als 1 Tag und nicht als 23 Stunden berechnet.inDaylightTime(last. import import import import java.JJJJ> " + "<Datum: TT.02. public class Start { public static void main(String args[]) { DateFormat parser = DateFormat. Wenn Sie für den Parameter leap den Wert false übergeben.2004 und dem 28.out.02. Die Differenz zwischen dem 27.02.DateFormat. So werden beispielsweise die Differenzen zwischen dem 28. werden demnach als 366 Jahre angerechnet.2004 und dem 01.2005 als genau 1 Jahr.

) Abbildung 27: Beispielaufrufe 47 Differenz zwischen zwei Datumswerten in Tagen berechnen Ein Tag besteht stets aus 24 Stunden.01. 24*60 Minuten. Stünde in obigem Code time1 für den 27. abfragt. dass die Sommerzeit nicht berücksichtigt wird. März 00:00 Uhr.abs((time2.setTime(parser. durch 1000 und weiter noch durch 24*60*60 dividiert? // Vereinfachter Ansatz: long diff = Math.JJJJ)"). time2. GMT. 24*60*60 Sekunden oder 24*60*60*1000 Millisekunden. Datum und Uhrzeit } catch(ParseException e) { System. time2.err. die keine Sommerzeit kennt (und zwar bevor in dem Objekt die gewünschte Zeit gespeichert wird). voneinander abzieht.140 >> Differenz zwischen zwei Datumswerten in Tagen berechnen time1.out. Um korrekte Ergebnisse zu erhalten. In letzterem Fall müssen die beiden Datumswerte als Gregorian- .parse(args[1])).1970 00:00:00 Uhr.getTimeInMillis())/1000).MM. long diffInDays = diff/(60*60*24).parse(args[0])).println("\n Kein gueltiges Datum (TT. true).getInstance(time1. Was liegt also näher.getTimeInMillis()-time1. } } } Listing 51: Differenz zwischen zwei Datumswerten berechnen (Forts. können Sie entweder die Zeitzone des Calendar-Objekts auf eine TimeZone-Instanz umstellen. true. indem man die in den Calendar-Objekten gespeicherte Anzahl Millisekunden seit dem 01. die Sommerzeitverschiebung manuell korrigieren (siehe Quelltext zu getInstance() aus Rezept 46) oder sich der in Rezept 46 definierten TimeSpan-Klasse bedienen. wäre diff lediglich gleich 23*60*60 und diffInDays ergäbe 0. als die Differenz zwischen zwei Datumswerten zu berechnen.setTime(parser. März 00:00 Uhr und time2 für den 28. ts = TimeSpan. System.println(" Differenz: " + ts). Das Problem an dieser Methode ist.

dass man das Datum. Einfacher noch geht es mit Hilfe der add()-Methode. ist für die Differenz in Tagen unerheblich.get(Calendar.set(Calendar. erhöhen. days). . Diese übergeben Sie dann an die Methode getInstance(). Tage zu einem Datum zu addieren oder von einem Datum abzuziehen. set() für das Feld Calendar. Tatsächlich ist in UTC (Coordinated Universal Time) nicht jeder Tag 24*60*60 Sekunden lang. das die Berücksichtigung der Schalttage bei der Berechnung der Jahre steuert. damit die Sommerzeit berücksichtigt wird. auf denen das Datum basiert.GregorianCalendar. true). // kein Java! // kein Java! Nicht selten sehen sich die Adepten dann getäuscht. System.DAY_OF_MONTH.out. Das Start-Programm zu diesem Rezept berechnet auf diese Weise die Differenz in Tagen zwischen zwei Datumseingaben.) Datum und Uhrzeit import java. Dezember oder 30 Juni eine Schaltsekunde eingefügt. ist daher. die über die Befehlszeile entgegengenommen werden.>> Datum und Uhrzeit 141 Calendar-Objekte vorliegen. (Das vierte Argument.getInstance(time1.DAY_OF_MONTH)) plus der zu addierenden Anzahl Tage (negativer Wert für Subtraktion) zu übergeben. Wochentag. time2.util. die die Überladung von Operatoren unterstützen. weil die zugrunde liegende Implementierung nicht die Anzahl Tage. Monat.und Uhrzeitfeldern (Jahr. date = date + 3.add(Calendar. Nun. Alle ein oder zwei Jahre wird am Ende des 31. Stunde etc. welches in einem Objekt einer Datumsklasse gekapselt ist. Diese Felder können mit Hilfe der get-/set-Methoden der Klasse (siehe Tabelle 15) abgefragt und gesetzt werden.DAY_OF_MONTH.) gespeichert. true. // gegeben GregorianCalendar time1 und time2 TimeSpan ts = TimeSpan. date.DAY_OF_MONTH) + days). Eine Möglichkeit. Diese Korrektur gleicht die auf einer Atomuhr basierende UTC-Zeit an die UT-Zeit (GMT) an. erwarten Programmieranfänger häufig. Wie aber kann man in Java Tage zu einem bestehenden Datum hinzuaddieren oder davon abziehen? Wie Sie mittlerweile wissen. date.DAY_OF_MONTH aufzurufen und diesem den alten Wert (= get(Calendar. A c h t u ng 48 Tage zu einem Datum addieren/subtrahieren In Sprachen. in Java gibt es keine überladenen Operatoren und obiger Fallstrick bleibt uns erspart. werden Datumswerte in Calendar-Objekten sowohl als Anzahl Millisekunden als auch in Form von Datums. so dass der Tag 24*60*60+1 Sekunden lang ist. sondern die Anzahl Millisekunden. Die Realität ist leider oftmals komplizierter. als wir Programmierer es uns wünschen. der Sie nur noch das Feld und die zu addierende Anzahl Tage (negativer Wert für Subtraktion) übergeben müssen: date. wobei Sie als drittes Argument unbedingt true übergeben.inDays()). die auf der Erdumdrehung beruht.println("Differenz: " + ts. mit Hilfe überladener Operatoren inkrementieren oder um eine bestimmte Zahl Tage erhöhen kann: ++date.

oder Unterlauf (die berechnete Anzahl Tage ist größer als die Tage im aktuellen Monat bzw. Mit der roll()-Methode schließlich können Sie den Wert eines Feldes ändern. da die Hybridimplementierung der Klasse GregorianCalendar standardmäßig Daten nach dem 15.2005 im julianischen Kalender repräsentiert? In diesem Fall reicht es nicht. Die set()-Methode passt die nächsthöhere Einheit nur dann an. wenn das private-Feld lenient auf true steht (Standardwert. . Datum und die hinzuzuaddierende Anzahl Tage werden als Argumente über die Befehlszeile übergeben. days). wenn lenient = true.DAY_OF_MONTH. int value) add(int field. welches den 27. int value) Arbeitsweise Anpassung der übergeordneten Einheit. passt add() die nächstgrößere Einheit an (für Tage also das MONTH-Feld). date. Ansonsten wird eine Exception ausgelöst. ansonsten Auslösen einer Exception Anpassung der übergeordneten Einheit Keine Anpassung der übergeordneten Einheit Tabelle 20: Methoden zum Erhöhen bzw. Monat (-1) und Tag zu übergeben.roll(Calendar. ein neues GregorianCalendar-Objekt erzeugen: GregorianCalendar jul = new GregorianCalendar(). Oktober 1582 als Daten im gregorianischen Kalender interpretiert (vergleiche Rezept 40). einfach dem GregorianCalendar-Konstruktor Jahr. ohne dass bei Über. int value) roll(int field. Abbildung 28: Addieren und Subtrahieren von Tagen 49 Datum in julianischem Kalender Sie benötigen ein Calendar-Objekt. Vermindern von Datumsfeldern Das Start-Programm demonstriert die Arbeit von add() und roll(). Stattdessen müssen Sie 1.oder Unterlauf das nächsthöhere Feld angepasst wird. Methode set(int field.142 >> Datum in julianischem Kalender Datum und Uhrzeit Kommt es zu einem Über. kleiner als 1).04. Einstellung über setLenient()).

Wenn Sie ein Datum im gregorianischen Kalender in das zugehörige Datum im julianischen Kalender umwandeln möchten.MAX_VALUE) einstellen: jul.setGregorianChange(new Date(Long.01.getDateInstance(DateFormat. müssen Sie beachten. 2. dass die DateFormat-Instanz standardmäßig mit einer GregorianCalendar-Instanz arbeitet. Um das korrekte julianische Datum zu erhalten.println(dfJul. (Der intern berechnete Millisekundenwert gibt also an. GMT. Stellen Sie dessen GregorianChange-Datum auf Date(Long. gc.setGregorianChange(new Date(Long.format(jul.>> Datum und Uhrzeit 143 2. 27). dfJul. Jahr. dessen GregorianChange-Datum auf Date(Long. entfernt liegen).01.FULL).MAX_VALUE) Wert übergeben.MIN_VALUE) ein: gc.MAX_VALUE)).1970 00:00:00 Uhr.getTimeInMillis()). 50 Achtung Umrechnen zwischen julianischem und gregorianischem Kalender Um ein Datum im julianischen Kalender in das zugehörige Datum im gregorianischen Kalender umzuwandeln (so dass beide Daten gleich viele Millisekunden vom 01. die für Datumswerte nach Oktober 1582 mit dem gregorianischen Kalender arbeitet.setTimeInMillis(c.1970 00:00:00 Uhr.setGregorianChange(new Date(Long.MAX_VALUE)).) Wenn Sie das Datum mittels einer DateFormat-Instanz in einen String umwandeln möchten. gehen Sie analog vor. 3. müssen Sie dem Formatierer eine Calendar-Instanz zuweisen. Datum und Uhrzeit jul. nur dass Sie setGregorianChange() den Date(Long. /** * Gregorianisches Datum in julianisches Datum unwandeln */ public static GregorianCalendar gregorianToJulian(GregorianCalendar c) { GregorianCalendar gc = new GregorianCalendar(). entfernt liegt.set(2005. beispielsweise also jul: DateFormat dfJul = DateFormat. Monat und Tag für das Objekt setzen: Das neue Objekt repräsentiert nun das gewünschte Datum im julianischen Kalender.MAX_VALUE)).getTime())). 3. wie viele Sekunden das Datum vom 01.setCalendar(jul). gehen Sie am besten wie folgt vor: 1.out. Listing 52: Methoden zur Umwandlung von Datumswerten zwischen julianischem und gregorianischem Kalender . Setzen sie die interne Millisekundenzeit des Objekts auf die Anzahl Millisekunden des julianischen Datums: gc. 3. GMT. die für alle Datumswerte nach dem julianischen Kalender rechnet. Erzeugen Sie ein neues GregorianCalendar-Objekt: GregorianCalendar gc = new GregorianCalendar(). System.

51 Ostersonntag berechnen Der Ostersonntag ist der Tag. DateFormat dfJul = DateFormat. Wenn Sie mit DateFormat Datumswerte umwandeln.getDateInstance(). Karfreitag. müssen Sie daher auch für das Calendar-Objekt der DateFormat-Instanz das GregorianChange-Datum umstellen – oder es einfach durch das GregorianCalendar-Objekt des Datums ersetzen: GregorianCalendar jul = new GregorianCalendar(). als er den Referenzpunkt für die Berechnung der österlichen Feiertage darstellt: Aschermittwoch.144 >> Ostersonntag berechnen gc. return gc.FULL).setCalendar(jul). gc. dass die DateFormat-Instanz das übergebene Datum als Date-Objekt übernimmt und mittels einer eigenen Calendar-Instanz in Jahr. Ostermontag.setTimeInMillis(c. Christi Himmelfahrt. . } Datum und Uhrzeit /** * Julianisches Datum in gregorianisches Datum unwandeln */ public static GregorianCalendar julianToGregorian(GregorianCalendar c) { GregorianCalendar gc = new GregorianCalendar(). DateFormat df = DateFormat.getDateInstance(DateFormat. jul. Pfingsten. müssen Sie beachten. an dem die Christen die Auferstehung Jesu Christi feiern. gc. für die Sie das GregorianChangeDatum umgestellt haben. umrechnet. Monat etc. welche jeweils in ihre julianische bzw. } Listing 52: Methoden zur Umwandlung von Datumswerten zwischen julianischem und gregorianischem Kalender (Forts. Gleichzeitig kennzeichnet er das Ende des österlichen Festkreises. Für den Programmierer ist der Ostersonntag insofern von zentraler Bedeutung.setTimeInMillis(c. return gc.setGregorianChange(new Date(Long.MIN_VALUE)). gregorianische Entsprechung umgerechnet werden.MAX_VALUE)).format(date. der mit dem Aschermittwoch beginnt.setGregorianChange(new Date(Long.getTime()). dfJul.getTimeInMillis()).) A cht un g Wenn Sie Datumswerte mittels einer DateFormat-Instanz in einen String umwandeln: GregorianCalendar date = new GregorianCalendar(). Gründonnerstag.getTimeInMillis()). String s = df. Das Start-Programm zu diesem Rezept liest ein Datum über die Befehlszeile ein und interpretiert es einmal als gregorianisches und einmal als julianisches Datum.

Bestrebungen.c + c/4. int n = year . dass der gregorianische Kalender erst im Oktober 1582.>> Datum und Uhrzeit 145 Der Ostertermin richtet sich nach dem jüdischen Pessachfest und wurde auf dem Konzil von Nicäa 325 festgelegt als: »Der 1. int a2 = a1 .util. int l2 = l1 . die Berechnung des Ostertermins zu vereinfachen und das Datum auf einen bestimmten Sonntag festzuschreiben. in vielen Ländern sogar noch später. int l1 = c . Mallen und Oudin.(c-k)/3 + 19*n + 15.30*(l1/30). } Listing 53: Berechnung des Ostersonntags im gregorianischen Kalender Achtung Wenn Sie historische Ostertermine berechnen. Heute gibt es eine Vielzahl von Algorithmen zur Berechnung des Ostersonntags.31*(month/4). int l3 = l2 . müssen Sie bedenken. int day = l + 28 .« – was auf der Nördlichen Halbkugel dem ersten Vollmond nach der Frühlings-Tag-und-Nachtgleiche entspricht. month-1. return new GregorianCalendar(year. dass diese nur nach geltender Konvention gültig sind. gibt es schon seit längerem.7 * (a1/7). /** * Datum des Ostersonntags im gregorianischen Kalender berechnen */ public static GregorianCalendar eastern(int year) { int c = year/100.GregorianCalendar.17)/25. Traditionell erfolgte die Berechnung mit Hilfe des Mondkalenders und der goldenen Zahl (die laufende Nummer eines Jahres im Mondzyklus). Wegen dieses Bezugs auf den Vollmond ist die Berechnung des Ostersonntags recht kompliziert.(l2/28)*(1 .(l2/28) * (29/(l2+1)) * ((21-n)/11)). int a1 = year + year/4 + l3 + 2 .a2. Bisher konnten die Kirchen diesbezüglich allerdings zu keiner Einigung kommen. siehe Tabelle 16 in Rezept 41. int k = (c . der dem ersten Pessach-Vollmond folgt. day). Datum und Uhrzeit . Sonntag. Die bekanntesten sind die Algorithmen von Carl Friedrich Gauß. eingeführt wurde.c/4 . int month = 3 + (l + 40)/44. Wenn Sie zukünftige Ostertermine berechnen. int l = l3 . Auf Letzterem basiert auch der in diesem Rezept implementierte Algorithmus: import java.19 * (year/19). müssen Sie bedenken.

. nach dem julianischen Kalender (siehe auch Rezept 49). day. dass GregorianChange-Datum für das zurückgelieferte Calendar-Objekt auf Date(Long.MAX_VALUE) gesetzt wurde. } else { month = 4. day = d+e-9. gc. sondern behielten diesen für die Berechnung des Ostersonntags sogar noch bis heute bei.setGregorianChange(new Date(Long.GregorianCalendar. 0).Date. Die meisten Länder stellten mit der Übernahme des gregorianischen Kalenders auch die Berechnung des Ostersonntags auf den gregorianischen Kalender um. day = 22+d+e. brauchen Sie daher nur die entsprechenden Felder abzufragen.util.set(year. day. } GregorianCalendar gc = new GregorianCalendar().h.MAX_VALUE)). import java. Die folgende Methode berechnet den Ostersonntag nach dem julianischen Kalender. int a = year%19. import java. int d = (19 * a + 15) % 30. Monat und Tag des Ostersonntags im julianischen Kalender aus dem zurückgelieferten Calendar-Objekt auslesen möchten. month-1.util. nach dem folglich auch Ostern berechnet wurde. return gc. 0.146 >> Ostersonntag berechnen Datum und Uhrzeit Ostern in der orthodoxen Kirche Vor der Einführung des gregorianischen Kalenders galt der julianische Kalender. beispielsweise: . siehe Tabelle 16 in Rezept 41. int e = (2*b + 4*c + 6*d + 6)%7. Sie hingen nicht nur lange dem julianischen Kalender an. der dem übergebenen Datum entspricht. d. /** * Datum des Ostersonntags im julianischen Kalender berechnen */ public static GregorianCalendar easternJulian(int year) { int month. int c = year%7. if ((d+e) < 10) { month = 3. } Listing 54: Berechnung des Ostersonntags im julianischen Kalender Beachten Sie. Wenn Sie Jahr. gc. int b = year%4. das Calendar-Objekt berechnet den Millisekundenwert. 0. Nicht so die orthodoxen Kirchen.

get(Calendar.console(). System. java. System. Um das korrekte julianische Datum zu erhalten. df.): %s\n". Das Start-Programm zu diesem Rezept demonstriert die Verwendung von MoreDate. müssen Sie beachten.FULL).getTimeInMillis()).YEAR) + " " + gc. Dann können Sie das Datum auch durch Abfragen der Datumsfelder auslesen: GregorianCalendar gc = new GregorianCalendar().DAY_OF_MONTH)). public class Start { public static void main(String args[]) { GregorianCalendar eastern.util.easternJulian().get(Calendar.DAY_OF_MONTH)). die für Datumswerte nach Oktober 1582 den gregorianischen Kalender zugrunde legt.easternJulian(year). beispielsweise also das von MoreDate.getTime())).out.MONTH) + " " + gc.getDateInstance(DateFormat. welchem Datum in unserem Kalender der orthodoxe Ostersonntag entspricht.out. java.printf("Orthod.println().GregorianCalendar.Date. müssen Sie dem Formatierer eine Calendar-Instanz zuweisen.easternJulian(year). 147 Wenn Sie das Datum des Ostersonntags im Julianischen Kalender mittels einer DateFormatInstanz in einen String umwandeln möchten.util.text. deren CalendarObjekt nicht umgestellt wurde: easternJ = MoreDate. Ostersonntag (Gregor. f.>> Datum und Uhrzeit GregorianCalendar easternJ = MoreDate.get(Calendar.util.printf("Orthod. Oder Sie erzeugen eine neue GregorianCalendar-Instanz und weisen dieser die Anzahl Millisekunden von easternJ zu. Dazu brauchen Sie easternJ nur mittels einer DateFormat-Instanz zu formatieren.eastern() und MoreDate.get(Calendar. Listing 55: Berechnung des Ostersonntags Datum und Uhrzeit .println(" " + gc.MONTH) + " " + easternJ.DateFormat. die für alle Datumswerte nach dem julianischen Kalender rechnet.out. DateFormat df = DateFormat. System. Ostersonntag (Julian. easternJ.Calendar. Sicherlich wird es Sie aber auch interessieren. System.format(easternJ.setCalendar(easternJ).easternJulian(year).format(easternJ.console().println(" " + easternJ. import import import import java.get(Calendar.YEAR) + " " + easternJ.get(Calendar. dass die DateFormatInstanz standardmäßig mit einer GregorianCalendar-Instanz arbeitet.easternJulian() zurückgelieferte Objekt: easternJ = MoreDate. int year = 0. df.getTime())). System. gc.setTimeInMillis(easternJ. java. Das Programm nimmt über die Befehlszeile eine Jahreszahl entgegen und gibt dazu das Datum des Ostersonntags aus.): %s\n".

getTime())).setCalendar(easternJ). denn Sie müssten lediglich für jeden Feiertag ein GregorianCalendar-Objekt erzeugen und dem Konstruktor Jahr. System. 2007 und 2008 52 Deutsche Feiertage berechnen Gäbe es nur feste Feiertage.format(easternJ. %s\n". Ostersonntag (julian. System.easternJulian(year).println(" Aufruf: Start <Jahreszahl>"). } } } Listing 55: Berechnung des Ostersonntags (Forts.printf(" Ostersonntag: df. Monat (0-11) und Tag des Feiertagsdatum übergeben.out.console(). df.148 >> Deutsche Feiertage berechnen if (args.eastern(year). } catch (NumberFormatException e) { System. df. } Datum und Uhrzeit try { year = Integer. wäre deren Berechnung ganz einfach – ja. df.): %s\n". // Ostersonntag berechnen eastern = MoreDate.printf("Orthod. eigentlich gäbe es gar nichts mehr zu berechnen. df.parseInt(args[0]).printf("Orthod.getTime())). .setCalendar(eastern).length != 1) { System.format(easternJ.) Abbildung 29: Ostersonntage der Jahre 2006.err. System. System.getTime())).): %s\n".console(). Ostersonntag (gregor. // Griech-orthodoxen Ostersonntag berechnen // (julianischer Kalender) easternJ = MoreDate.format(eastern.println(" Ungueltiges Argument").console().exit(0).

Advent 3. Januar Ostersonntag – 48 Tage Ostersonntag – 47 Tage Ostersonntag – 46 Tage 14. November 6. Advent Ostermontag Maifeiertag Himmelfahrt Muttertag Pfingstsonntag Pfingstmontag Fronleichnam* Mariä Himmelfahrt* Tag der deutschen Einheit Reformationstag* Allerheiligen* Allerseelen Nikolaus Sankt Martinstag Volkstrauertag Buß. Sonntag im Mai Ostersonntag + 49 Tage Ostersonntag + 50 Tage Ostersonntag + 60 Tage 15. Oktober 1. Feiertag Neujahr Heilige drei Könige* Rosenmontag Fastnacht Aschermittwoch Valentinstag Gründonnerstag Karfreitag Ostersonntag abhängig von – – Ostersonntag Ostersonntag Ostersonntag – Ostersonntag Ostersonntag Pessach-Vollmond Datum 01. Dezember 11. Advent Ostersonntag – Ostersonntag 1. November Sonntag vor Totensonntag Mittwoch vor Totensonntag 7 Tage vor 1. Advent 7 Tage vor 2. Januar 06. der dem ersten Pessach-Vollmond folgt (siehe Rezept 51) Ostersonntag + 1 Tag 1. aber wir wollen in diesem Rezept auch die Tage berücksichtigen. Advent 2. Advent 7 Tage vor 4. Advent 7 Tage vor 3. denen eine besondere Bedeutung zukommt. dann die Gruppe der Feiertage. Sonntag. die von Weihnachten abhängen. Februar Ostersonntag – 3 Tage Ostersonntag – 2 Tage 1. die von Osten abhängen.und Bettag* Totensonntag 1.>> Datum und Uhrzeit 149 Fakt ist aber. und schließlich noch der Muttertag. Oktober 31. Da wären zum einen die große Gruppe der Feiertage. regionale Feiertage sind mit * gekennzeichnet) . September 3. Mai Ostersonntag + 39 Tage 2. Datum und Uhrzeit Letzterer ist im Übrigen kein echter Feiertag. auch wenn es sich nicht um gesetzliche Feiertage handelt. Mai Ostersonntag Ostersonntag Ostersonntag – – – – – – – Heiligabend Heiligabend Heiligabend Heiligabend Heiligabend Heiligabend Tabelle 21: Deutsche Feiertage (gesetzliche Feiertage sind farbig hervorgehoben. dass ungefähr die Hälfte aller Feiertage beweglich sind. November 2.

public CalendarDay(String name.time= time. Die Klasse CalendarDay Die Klasse CalendarDay speichert zu jedem Feiertag den Namen.comment = comment. /** * Klasse zum Speichern von Kalenderinformationen zu Kalendertagen */ public class CalendarDay { private private private private private String name. this. boolean nationwide. ob es sich um einen gesetzlichen nationalen Feiertag handelt oder ob es ein regionaler Feiertag ist. } public String getName() { return name.150 >> Deutsche Feiertage berechnen Feiertag 4. die für ein gegebenes Jahr alle Feiertage berechnet und in einer Vector-Collection speichert. this. bereitet die Berechnung der Osterfeiertage. Dezember 25. Doch glücklicherweise haben wir dieses Problem bereits im Rezept 51 gelöst. insbesondere die Berechnung des Ostersonntags.name = name. Der hier präsentierte Ansatz basiert auf zwei Klassen: einer Klasse CalendarDay. Advent Heiligabend Datum und Uhrzeit 1. this. Listing 56: Die Klasse CalendarDay . boolean nationwide.holiday = holiday. Weihnachtstag 2. long time. regionale Feiertage sind mit * gekennzeichnet) (Forts.nationwide = nationwide. boolean holiday. Dezember Tabelle 21: Deutsche Feiertage (gesetzliche Feiertage sind farbig hervorgehoben. einen optionalen Kommentar. long time. String comment. boolean holiday. und einer Klasse Holidays. Weihnachtstag Silvester abhängig von Heiligabend – – – – Datum Sonntag vor Heiligabend 24. Dezember 31. Dezember 26. deren Objekte die einzelnen Feiertage repräsentieren. das Datum (als Anzahl Millisekunden).) Wie Sie der Tabelle entnehmen können. String comment) { this. Die Berechnung der Feiertage reduziert sich damit weitgehend auf die Erzeugung und Verwaltung der Feiertagsdaten. this. die größte Schwierigkeit.

und für jeden Feiertag ein CalendarDay-Objekt erzeugt. GregorianCalendar tmp.util. import java. Die CalendarDay-Objekte werden zusammen in einer Vector-Collection gespeichert. Listing 57: Aus Holidays. public Holidays(int year) { // Ostern vorab berechnen GregorianCalendar eastern = eastern(year).>> Datum und Uhrzeit 151 } public long getTime() { return time.) Die Klasse Holidays Die Klasse berechnet und verwaltet die Feiertage eines gegebenen Jahres. } public String getComment() { return comment. } public boolean getHoliday() { return holiday.GregorianCalendar. H i n we i s Die Methode eastern().java Datum und Uhrzeit .util. import java. der daraufhin berechnet. /** * Berechnet Feiertage eines Jahres und speichert die gewonnenen * Informationen in einer Vector-Collection von CalendarDay-Objekten */ public class Holidays { Vector<CalendarDay> days = new Vector<CalendarDay>(34). auf welche Datumswerte die Feiertage fallen.Calendar.Vector. } } Listing 56: Die Klasse CalendarDay (Forts. ist in Holidays definiert und identisch zu der Methode aus Rezept 51. Das Jahr übergeben Sie als int-Wert dem Konstruktor. } public boolean getNationwide() { return nationwide. import java. die vom Konstruktor zur Berechnung des Ostersonntags verwendet wird.util.

+1).add(new CalendarDay("Ostersonntag".add(new CalendarDay("Gründonnerstag". tmp = (GregorianCalendar) eastern.add(new CalendarDay("Himmelfahrt".clone(). "")). true.getTimeInMillis().add(new CalendarDay("Fastnacht". days.14)). Sonntag in Mai Listing 57: Aus Holidays. days.add(new CalendarDay("Rosenmontag". +39). true. false. days.getTimeInMillis(). tmp. tmp. false. -48). days.getTimeInMillis().1. "")). +1). days.152 >> Deutsche Feiertage berechnen int day.getTimeInMillis().getTimeInMillis(). days. tmp.1)).add(new CalendarDay("Aschermittwoch".")).4. "in Baden-Würt. tmp. tmp. tmp = (GregorianCalendar) eastern. tmp.add(Calendar. false.getTimeInMillis(). tmp.add(new CalendarDay("Karfreitag".DAY_OF_MONTH. false. eastern.add(Calendar. // Muttertag = 2.6)). Bayern und Sachsen-A.DAY_OF_MONTH.add(new CalendarDay("Neujahr". "")). tmp.add(new CalendarDay("Maifeiertag". days. tmp. days. true. tmp. false. tmp. true. tmp = (GregorianCalendar) eastern.) Datum und Uhrzeit . "")). +1). false. +1). true. tmp.getTimeInMillis().getTimeInMillis(). true. (new GregorianCalendar(year. true.add(new CalendarDay("Valentinstag".0.DAY_OF_MONTH.add(Calendar.add(Calendar. days. tmp = (GregorianCalendar) eastern.DAY_OF_MONTH. "")).DAY_OF_MONTH.getTimeInMillis(). days. "")).add(Calendar. false.getTimeInMillis(). "")). true. false. true. false..add(Calendar. false. false.java (Forts.clone().0.clone(). true.DAY_OF_MONTH.1)). (new GregorianCalendar(year.getTimeInMillis(). "")).DAY_OF_MONTH.add(new CalendarDay("Ostermontag". (new GregorianCalendar(year. "")). false. true. (new GregorianCalendar(year. true. -3). "")).add(Calendar. tmp. days.getTimeInMillis(). "")).add(new CalendarDay("Heilige drei Könige".clone(). tmp. days.

clone(). true. "")).getTimeInMillis(). "")). days. Saarland.3)). "in Brandenburg.15)).4. true.T. false.get(Calendar.add(new CalendarDay("Pfingstmontag". tmp. false.getTimeInMillis(). Sachsen. days. days.getTimeInMillis(). 11. 25).. false. false. Bayern.getTimeInMillis().DAY_OF_MONTH. NRW. false. +1).add(new CalendarDay("Fronleichnam". Bayern.10. (new GregorianCalendar(year. false. und Thüringen")). Advent = 1. "in Baden-Würt.add(Calendar. days. Listing 57: Aus Holidays.11)).add(new CalendarDay("Pfingstsonntag". 1).DAY_OF_WEEK). Meckl. days. true.7. tmp. " + "Rheinl. (new GregorianCalendar(year.getTimeInMillis().getTimeInMillis(). days.add(new CalendarDay("Reformationstag".10. Weihnachtstag GregorianCalendar advent = new GregorianCalendar(year.DAY_OF_MONTH. false. Hessen. "")).>> Datum und Uhrzeit 153 tmp = (GregorianCalendar) eastern. +49). tmp. "")). . true. days.-Pfalz. (new GregorianCalendar(year.add(Calendar. "")). tmp.add(new CalendarDay("Maria Himmelfahrt".-Vorp.add(Calendar.31)). days.T. "")).1)).. " + "Rheinl. day = firstMay. Sonntag vor 1.day)). Gemeinden " + "von Bayern")).) " + "und Thüringen (z.. (new GregorianCalendar(year.add(new CalendarDay("Martinstag". +10).9.java (Forts. // ab hier nicht mehr chronologisch // 4.add(new CalendarDay("Allerheiligen". (new GregorianCalendar(year. else day = 1 + (8-day) + 7. false. if (day == Calendar. days. (new GregorianCalendar(year.2)). " + "Sachsen-A. 4.) Datum und Uhrzeit GregorianCalendar firstMay = new GregorianCalendar(year.add(new CalendarDay("Tag der Einheit". true.getTimeInMillis(). tmp. true. "in Saarland und kathol. Sachsen (z. "in Baden-Würt. days.10.SUNDAY) day = 1 + 7. false.add(new CalendarDay("Muttertag".DAY_OF_MONTH.getTimeInMillis(). true.getTimeInMillis().9.)")). (new GregorianCalendar(year. tmp.getTimeInMillis(). NRW.add(new CalendarDay("Allerseelen". true. true. false.-Pfalz und Saarland")). false.

Advent". tmp.getTimeInMillis(). tmp.getTimeInMillis().get(Calendar.getTimeInMillis(). advent. "")). advent.DAY_OF_WEEK) == Calendar.add(Calendar.add(Calendar.get(Calendar.getTimeInMillis(). false. Advent". Advent advent. // 1.add(Calendar. (new GregorianCalendar(year. -7). Advent". false.add(Calendar.add(new CalendarDay("3. days.getTimeInMillis(). false. Advent = Eine Woche vor 2.add(Calendar.DAY_OF_WEEK). day). false.11. false. false. Advent advent. -7).DAY_OF_MONTH. false. -7). false. Listing 57: Aus Holidays.add(Calendar.get(Calendar. // 3.) .DAY_OF_MONTH.add(new CalendarDay("1. false. "")). tmp. Advent tmp = (GregorianCalendar) advent.add(Calendar. false. Advent". days. -advent. -7).add(new CalendarDay("Totensonntag".getTimeInMillis(). tmp. days. "Sachsen")). "")).DAY_OF_MONTH. days. days. // Volkstrauertag = Sonntag vor Totensonntag tmp.DAY_OF_MONTH. // Buß.6)). if (day == Calendar. Advent advent. -7). advent.clone(). Advent = Eine Woche vor 3. Advent = Eine Woche vor 4. false. days. advent. "")). "")).und Bettag = Mittwoch vor Totensonntag day = tmp.java (Forts.DAY_OF_MONTH.add(new CalendarDay("Nikolaus". false.154 >> Deutsche Feiertage berechnen Datum und Uhrzeit if (advent.add(new CalendarDay("4. days.DAY_OF_WEEK)+1).add(new CalendarDay("Buß. false. // 2. else advent.DAY_OF_MONTH.getTimeInMillis(). tmp.add(new CalendarDay("2.DAY_OF_MONTH.add(new CalendarDay("Volkstrauertag". days.SUNDAY) advent.add(Calendar. // Totensonntag = Sonntag vor 1.und Bettag". -7).DAY_OF_MONTH.getTimeInMillis(). else day = (4-day).WEDNESDAY) day = -(4+day). false. "")).

"")). true.11. false. if (name.equals("Gruendonnerstag")) name = "Gründonnerstag". wenn auf das übergebene Datum ein gesetzlicher. Wurde kein passendes CalendarDay-Objekt gefunden.equals("Heilige drei Koenige")) name = "Heilige drei Könige". (new GregorianCalendar(year. Weihnachtstag". false. mit denen der Benutzer Informationen über die Feiertage einholen kann: CalendarDay searchDay(String name) CalendarDay-Objekt und liefert es zurück. (new GregorianCalendar(year. boolean isNationalHoliday(GregorianCalendar date) Liefert true zurück.24)).25)). regionaler Feiertag fällt.add(new CalendarDay("1.java (Forts. (new GregorianCalendar(year. "")).equals("Christi Himmelfahrt")) name = "Himmelfahrt". // Liefert das CalendarDay-Objekt zu einem Feiertag public CalendarDay searchDay(String name) { // Alternative Namen berücksichtigen if (name.equals("Tag der Arbeit")) name = "Maifeiertag". false. true. boolean isRegionalHoliday(GregorianCalendar date) Liefert true zurück. days.add(new CalendarDay("Heiligabend". . Weihnachtstag". true. if (name. true..getTimeInMillis(). "")).getTimeInMillis().26)). false. Alternative Namen werden zum Teil berücksichtigt.31)).. liefert die Methode null zurück.11.11. definiert sie verschiedene Methoden. days. days. "")).getTimeInMillis().getTimeInMillis(). null.add(new CalendarDay("2. Listing 57: Aus Holidays. wenn auf das übergebene Datum ein gesetzlicher. false. "")). CalendarDay getDay(GregorianCalendar date) Sucht zu einem gegebenen Feiertagsnamen (beispielsweise »Allerheiligen« das zugehörige Liefert zu einem gegebenen Datum das zugehörige CalendarDay-Objekt zurück bzw..add(new CalendarDay("Silvester". if (name. days.>> Datum und Uhrzeit 155 false. Listing 58: Die Klasse Holidays Datum und Uhrzeit . } .) Damit man mit der Klasse Holidays auch vernünftig arbeiten kann.11.. wenn kein passendes Objekt gefunden wurde. (new GregorianCalendar(year. nationaler Feiertag fällt.

} // Liefert das CalendarDay-Objekt zu einem Kalenderdatum public CalendarDay getDay(GregorianCalendar date) { for(CalendarDay d : days) { if( d.) Datum und Uhrzeit . ob das angegebene Datum auf einen gesetzlichen. if (name. Weihnachtstag". Advent". if (name.und Bettag")) name = "Buß. if (name. Advent".equals("Zweiter Weihnachtstag")) name = "2. if (name. for(CalendarDay d : days) { if (name. Advent".getNationwide() ) return true.und Bettag".equals("Erster Advent")) name = "1. } return null.equals("Tag der deutschen Einheit")) name = "Tag der Einheit". if (name. Listing 58: Die Klasse Holidays (Forts.equals("Vatertag")) name = "Himmelfahrt".getTime() == date.equals("Sankt Martin")) name = "Martinstag".equals("Bettag")) name = "Buß. if (name. Weihnachtstag".equals(d. if (name.getHoliday() && d. Advent".equals("Dritter Advent")) name = "3.equals("Vierter Advent")) name = "4.getTimeInMillis() ) return d.und Bettag".getTimeInMillis() && d.equals("Weihnachtsabend")) name = "Heiligabend".equals("Erster Weihnachtstag")) name = "1.156 >> Deutsche Feiertage berechnen if (name. nationalen // Feiertag fällt public boolean isNationalHoliday(GregorianCalendar date) { for(CalendarDay d : days) { if( d.getName()) ) return d. if (name.equals("Zweiter Advent")) name = "2. if (name. if (name.equals("Buss. } return null. if (name. } // Stellt fest.getTime() == date.

auf welches Datum ein bestimmter Feiertag im übergebenen Jahr fällt.2 Abbildung 30: Mit Start2 können Sie sich Feiertage in beliebigen Jahren2 berechnen lassen. Mit Start1 können Sie die Feiertage eines Jahres auf die Konsole ausgeben oder in eine Datei umleiten: java Start1 > Feiertage.getNationwide() ) return true. ob das angegebene Datum auf einen gesetzlichen.txt Mit Start2 können Sie abfragen. über die Befehlszeile entgegen. Beide nehmen die Jahreszahl. für die sie ein Holidays-Objekt erzeugen.getTime() == date. } return false. Der Name des Feiertags wird vom Programm abgefragt. dass es Bestrebungen gibt. (Denken Sie beispielsweise daran. regionalen // Feiertag fällt public boolean isRegionalHoliday(GregorianCalendar date) { for(CalendarDay d : days) { if( d.getHoliday() && !d. } // Stellt fest. } public static GregorianCalendar eastern(int year) { // siehe Rezept 51 } } Listing 58: Die Klasse Holidays (Forts. die entsprechenden Feiertage gibt es in dem betreffenden Jahr und an ihrer Berechnung hat sich nichts geändert.>> Datum und Uhrzeit 157 } return false. 2.) . Immer vorausgesetzt.getTimeInMillis() && d.) Datum und Uhrzeit Zu diesem Rezept gibt es zwei Start-Programme. das Osterdatum festzuschreiben.

WEDNESDAY. ist im DAY_OF_WEEK-Feld des Calendar-Objekts gespeichert. System. welches den Wochentag zu einem beliebigen Datum ermittelt.MM.MEDIUM. "DONNERSTAG". GregorianCalendar date = new GregorianCalendar().ParseException. während Arrays mit 0 beginnend indiziert werden. demonstriert diese Technik: import import import import import java. FRIDAY oder SATURDAY.util.158 >> Ermitteln.util.GERMANY). THURSDAY. DAY_OF_WEEK) zurückgelieferten String als Index in dieses Array zu verwenden. "MITTWOCH". java.get(Calendar.Calendar. } Listing 59: Programm zur Berechnung des Wochentags . »Montag« etc.Locale. int day. Locale. } try { date. TUESDAY. "SAMSTAG" }. Den Wert des Felds können Sie für ein bestehendes Calendar-Objekt date wie folgt abfragen: int day = date.out. java.parse(args[0])).exit(0). "MONTAG".) ist es am einfachsten. public class Start { public static void main(String args[]) { DateFormat df = DateFormat.DateFormat. String[] weekdayNames = { "SONNTAG".getDateInstance(DateFormat.JJJJ>").out. dass die DAY_OF_WEEK-Konstanten den Zahlen von 1 (SUNDAY) bis 7 (SATURDAY) entsprechen. java. System. java.setTime(df. Sie müssen allerdings beachten.util.text.println("\n Kein gueltiges Datum (TT. System.JJJJ)"). MONDAY. if (args. Für die Umwandlung der DAY_OF_WEEK-Konstanten in Strings (»Sonntag«. day = date.DAY_OF_WEEK). "FREITAG".text.println(" Wochentag: " + weekdayNames[day-1]). welchen Wochentag ein Datum repräsentiert Datum und Uhrzeit Welchem Wochentag ein Datum entspricht.get(Calendar. "DIENSTAG". Für Instanzen von GregorianCalendar enthält dieses Feld eine der Konstanten SUNDAY.println(). ein Array der Wochentagsnamen zu definieren und den von get(Calendar.length != 1) { System. } catch(ParseException e) { System.println(" Aufruf: Start <Datum: TT.GregorianCalendar.DAY_OF_WEEK).out.err. welchen Wochentag ein Datum repräsentiert 53 Ermitteln.MM. Das Start-Programm zu diesem Rezept.

out. Trifft auch dies nicht zu. GregorianCalendar date = new GregorianCalendar(2005. können Sie mit isRegionalHoliday() prüfen. Datum und Uhrzeit .out. Holidays holidays = new Holidays(2005). 3. ob es ein regionaler gesetzlicher Feiertag ist.) Abbildung 31: Kann ein Maifeiertag schlechter liegen? 54 Ermitteln. Dann erzeugen Sie ein GregorianCalendar-Objekt für das zu untersuchende Datum.>> Datum und Uhrzeit 159 } } Listing 59: Programm zur Berechnung des Wochentags (Forts.println("\t Nationaler Feiertag "). } else System. ob es sich um einen Feiertag handelt.isRegionalHoliday(date)) { System. } else if (holidays.getDay(date) != null) { System.out. Zuerst erzeugen Sie für das gewünschte Jahr ein Holidays-Objekt. können Sie mit getDay() prüfen. 3. dass Sie zuerst durch Aufruf von isNationalHoliday() prüfen.isNationalHoliday(date)) { System. 2. ob es sich um einen nationalen gesetzlichen Feiertag handelt. 23). 1. ob es sich bei einem bestimmten Tag im Jahr um einen Feiertag handelt. Bezeichnet ein Datum einen Tag.println("\t Regionaler Feiertag " ).println("\t Kein Feiertag "). können Sie sich mit getDay() die Referenz auf das zugehörige CalendarDay-Objekt zurückliefern lassen und die für den Tag gespeicherten Informationen abfragen. ob ein Tag ein Feiertag ist Mit Hilfe der Klasse Holidays aus Rezept 52 können Sie schnell prüfen. Wenn nicht. der im Holidays-Objekt gespeichert ist.println("\t Besonderer Tag " ). ob der Tag überhaupt als besonderer Tag in dem holidays-Objekt gespeichert ist: if(holidays.out. } else if(holidays. Sie können dabei beispielsweise so vorgehen. Schließlich prüfen Sie mit Hilfe der entsprechenden Methoden des Holidays-Objekts.

ob ein Tag ein Feiertag ist 55 Ermitteln. Abbildung 33: 1900 ist kein Schaltjahr. int year = 2005. ob ein Jahr ein Schaltjahr ist Datum und Uhrzeit Abbildung 32: Ermitteln. die Jahreszahl wird vielmehr als Argument an den int-Parameter übergeben. 2000 ist ein Schaltjahr. so dass Sie zum Aufruf ein GregorianCalendar-Objekt benötigen. Dieses muss aber nicht das zu prüfende Jahr repräsentieren. weil es durch 100 teilbar ist. date.160 >> Ermitteln. obwohl es durch 100 teilbar ist. ob ein Jahr ein Schaltjahr ist Ob ein gegebenes Jahr im gregorianischen Kalender ein Schaltjahr ist. GregorianCalendar date = new GregorianCalendar(). weil es ein Vielfaches von 400 darstellt. Leider ist die Methode nicht statisch. Mit dem Start-Programm zu diesem Rezept können Sie prüfen. lässt sich bequem mit Hilfe der Methode isLeapYear() feststellen. ob ein Jahr im gregorianischen Kalender ein Schaltjahr ist. .isLeapYear(year).

get(Calendar. Konkret: Für das Geburtsdatum 01. Das berechnete Alter wäre daher fälschlicherweise 50 statt 49.DAY_OF_ YEAR) in beiden Fällen 60 zurückliefern. ist diese Aufgabe auch relativ schnell durch Differenzbildung der Jahreszahlen erledigt. . die am birthdate * geboren wurde. Wenn ja. ob der Monat im Vergleichsjahr kleiner als der Monat im Geburtsjahr ist.get(Calendar. Wenn lediglich das Jahr der »Geburt« bekannt ist. siehe Rezept 11. beispielsweise dem aktuellen Datum. dass das Alter unter Umständen um 1 geringer ist als die Differenz der Jahreszahlen – dann nämlich. // Alter um 1 Jahr vermindern if ( (otherDate. welches Alter eine Person. // Jahresunterschied berechnen age = otherDate. das Vergleichsdatum ist das aktuelle Datum.YEAR).MONTH) < birthdate.DAY_OF_MONTH))) --age.get(Calendar. sei es nun das Alter eines Kunden. Beachten Sie auch die Formatierung der Ausgabe mit ChoiceFormat. ist eine recht häufige Aufgabe. ob Tag in otherDate vor Tag in birthdate liegt. der Tag im Monat des Vergleichsjahrs kleiner dem Tag im Monat des Geburtsjahrs ist. Calendar otherDate) { int age = 0.02.get(Calendar. einer Ware oder einer beliebigen Sache (wie z. Das Geburtsdatum wird im Programmverlauf abgefragt. wenn das Vergleichsdatum in seinem Jahr weiter vorne liegt als das Geburtsdatum im Geburtsjahr. // anderes Datum liegt vor Geburtsdatum if (otherDate. return age. müssen Sie beachten.get(Calendar. Könnte man nicht einfach das Feld DAY_OF_YEAR für beide Daten abfragen und vergleichen? Die Antwort ist nein.get(Calendar.MONTH)) ||(otherDate. am otherDate hat */ public static int age(Calendar birthdate. zu vergleichen. oder.1955 und ein Vergleichsdatum 29. dass die Methode so scheinbar umständlich prüft.MONTH) == birthdate.birthdate.YEAR) .03. } Vielleicht wundert es Sie. // Prüfen.get(Calendar.DAY_OF_MONTH) < birthdate. wie alt eine Person oder ein Gegenstand heute ist. falls die Monate gleich sind. Die Methode age() berücksichtigt dies: /** * Berechnet.before(birthdate)) return -1.>> Datum und Uhrzeit 161 56 Alter aus Geburtsdatum berechnen Liegt jedoch das komplette Geburtsdatum vor und ist dieses mit einem zweiten Datum.MONTH) && otherDate.B.get(Calendar. weil dann Schalttage das Ergebnis verfälschen können.2004 würde get(Calendar. Mit dem Start-Programm zu diesem Rezept können Sie berechnen. Datum und Uhrzeit Die Berechnung des Alters. Erfindungen).

java.Locale.err. System.util.next().println("\n Sie sind " + age + " " + cf.out.util.setTime(df.Scanner. java.out.MM.MM. if (age < 0) { System. 2}. Calendar. "Jahr".out.JJJJ)").getInstance()). } else { double[] limits = {0.GERMANY). try { System. 1. java.Calendar.util.JJJJ ein: ").GregorianCalendar.println("\n Sie sind noch nicht geboren").DateFormat. ChoiceFormat cf = new ChoiceFormat(limits. Scanner sc = new Scanner(System. String input = sc.print("\n Geben Sie Ihr Geburtsdatum im Format " + " TT.parse(input)).MEDIUM.text. java.age(date. java. public class Start { public static void main(String args[]) { DateFormat df = DateFormat. "Jahre"}. GregorianCalendar date = new GregorianCalendar(). String[] outputs = {"Jahre".getDateInstance(DateFormat.text. } } catch(ParseException e) { System.java – Programm zur Altersberechnung .util.println("\n Kein gueltiges Datum (TT. // Vergleich mit aktuellem Datum age = MoreDate. } } } Listing 60: Start.text.ParseException.162 >> Alter aus Geburtsdatum berechnen Datum und Uhrzeit import import import import import import import java.in). java.format(age) + " alt"). Locale. outputs).ChoiceFormat. int age = 0. date.

siehe Rezept 41.MONTH).out.println( s ). Date today = new Date().util.Date. Sie können ein Calendar-Objekt erzeugen und auf eine beliebige Zeit setzen: Calendar calendar = Calendar. m. 20.getInstance().DATE). Sofern Sie die Uhrzeit nicht nur ausgeben oder bestenfalls noch mit anderen Uhrzeiten des gleichen Tags vergleichen möchten. die aktuelle Zeit abzufragen. Sie können die Zeit aus einem Date-Objekt an ein Calendar-Objekt übergeben: Calendar calendar = Calendar.getInstance(). lassen Sie sich von DateFormat. h.getInstance().get(Calendar. calendar. 12.YEAR). System.set(calendar. Sie können ein GregorianCalender-Objekt für eine bestimmte Uhrzeit erzeugen: GregorianCalendar gCal = // year. . besteht darin. min. Ausgabe: Thu Mar 31 10:54:31 CEST 2005 Wenn Sie lediglich die Zeit ausgeben möchten. System. 12. ein Objekt der Klasse Date zu erzeugen: Datum und Uhrzeit // Ausgabe: 10:54:31 // Datum // beibehalten // Uhrzeit setzen import java.getTimeInstance() ein entsprechendes Formatierer-Objekt zurückliefern und übergeben Sie das DateObjekt dessen format()-Methode. 30. calendar. Sie können sich mit getInstance() ein Calendar-Objekt zurückliefern lassen. 4. calendar. sollten Sie die Uhrzeit durch ein Calendar-Objekt (siehe auch Rezept 39) repräsentieren. welches die aktuelle Zeit (natürlich inklusive Datum) repräsentiert: Calendar calendar = Calendar.get(Calendar. H in we is Mehr zur Formatierung mit DateFormat. 1).out.format(today).get(Calendar. Als Ergebnis erhalten Sie einen formatierten Uhrzeit-String zurück. d.println(today).getTimeInstance(). String s = DateFormat. 30. sec new GregorianCalendar(2005.>> Datum und Uhrzeit 163 57 Aktuelle Zeit abfragen Der einfachste und schnellste Weg. 1).

würden Sie schreiben: // Formatierer DateFormat df = DateFormat. Registrieren Sie das TimeZone-Objekt beim Formatierer. wird die Anzahl Millisekunden gemäß dem gültigen Kalender und gemäß der auf dem aktuellen System eingestellten Zeitzone in Datums.FULL.01. Stunde.getTimeZone("America/Los_Angeles").println(" America/Los Angeles: " + df. // 2.getTimeInstance(). die Daten des Calendar-Objekts als Date-Objekt an die format()-Methode zu übergeben: df. Wenn Sie diese Zeitangabe in einen formatierten String umwandeln lassen (mittels DateFormat oder SimpleDateFormat. wie viel Uhr es aktuell in Los Angeles ist. Tag.getTime()). Umrechnung (und Ausgabe) in Zeitzone für Los Angeles (Amerika) System. Wenn die Uhrzeit in Form eines Calendar-Objekts vorliegt. Zeitzone erzeugen TimeZone tz = TimeZone. Minute etc. Erzeugen Sie ein TimeZone-Objekt für die gewünschte Zeitzone.setTimeZone(tz). // 1. gehen Sie wie folgt vor: 1.format(calendar.164 >> Zeit in bestimmte Zeitzone umrechnen Datum und Uhrzeit Um die in einem Calendar-Objekt gespeicherte Uhrzeit auszugeben. // Aktuelles Datum Date today = new Date(). Sie müssen lediglich daran denken. DateFormat. gehen Sie analog vor.format(today)). wird das Objekt mit der Anzahl Millisekunden initialisiert. Um beispielsweise zu berechnen. können Sie entweder die Werte für die einzelnen Uhrzeit-Felder mittels der zugehörigen get-Methoden abfragen (siehe Tabelle 15 aus Rezept 40) und in einen String/Stream schreiben oder sie wandeln die Feldwerte durch Aufruf von getTime() in ein Date-Objekt um und übergeben dieses an die format()-Methode einer DateFormat-Instanz: String s = DateFormat.1970 00:00:00 Uhr.getDateTimeInstance(DateFormat. GMT. die seit dem 01.und Zeitfelder (Jahr. .) umgerechnet. 2.FULL). vergangen sind. Formatierer auf Zeitzone umstellen Wenn Sie die Zeit dagegen in die Zeit einer anderen Zeitzone umrechnen lassen möchten. 58 H in we is Hi nwei s Zeit in bestimmte Zeitzone umrechnen Wenn Sie mit Hilfe von Date oder Calendar die aktuelle Zeit abfragen (siehe Rezept 57). Wandeln Sie die Zeitangabe mit Hilfe des Formatierers in einen String um.format(calObj. siehe Rezept 41). // 3.getTime()) ). Zeitzone beim Formatierer registrieren df. Monat.out. 3.

die seit dem 01. siehe Tabelle 15 aus Rezept 40 – liefern daraufhin die der Zeitzone entsprechenden Werte (inklusive Zeitverschiebung und Berücksichtigung der Sommerzeit) zurück. In diesem Fall übergeben Sie das TimeZone-Objekt.getTimeZone("America/Los_Angeles").getTimeZone("Europe/Berlin").. welches die Zeitzone repräsentiert. vor allem nicht bei der Formatierung der Zeitwerte mittels DateFormat. Minuten etc.DST_OFFSET).1970 00:00:00 Uhr. America/Denver US/Central. gespeichert. America/Chicago.. Zeitzone Samoa Normalzeit Hawaii Normalzeit Alaska Normalzeit Pazifische Normalzeit Rocky Mountains Normalzeit Zentrale Normalzeit entspricht GMT GMT-11:00 GMT-10:00 GMT-09:00 GMT-08:00 GMT-07:00 Pacific/Samoa US/Hawaii US/Alaska US/Pacific. wird dieses Date-Objekt auf der Grundlage der intern gespeicherten Anzahl Millisekunden erzeugt. GMT vergangen sind. America/Los_Angeles US/Mountain. Canada/Pacific.setTimeZone(calendar. müssen Sie die Zeitzone des CalendarObjekts zuvor beim Formatierer registrieren: df. Vergessen Sie dies nie. 59 Achtung ID Zeitzone erzeugen Zeitzonen werden in Java durch Objekte vom Typ der Klasse TimeZone repräsentiert. die intern aus der Anzahl Millisekunden unter Berücksichtigung der Zeitzone berechnet werden. calendar. Es ändern sich lediglich die Datumsfeldwerte. Canada/Mountain. wird intern als Anzahl Millisekunden. calendar. Dieser Wert wird durch die Umstellung auf eine Zeitzone nicht verändert. . lassen Sie sich TimeZone-Objekte von der statischen Methode getTimeZone() zurückliefern. der Sie als Argument den ID-String der gewünschten Zeitzone übergeben: TimeZone tz = TimeZone.get(calendar.getTimeZone()). wie Stunden.01. America/ Mexico_City Tabelle 22: Zeitzonen GMT-06:00 . TimeZone tz = TimeZone. die ein Calendar-Objekt repräsentiert.HOUR_OF_DAY).setTimeZone(tz).getInstance(). Da TimeZone selbst abstrakt ist. Die Zeit. das Sie der format()-Methode von DateFormat übergeben können..B.>> Datum und Uhrzeit 165 Calendar auf Zeitzone umstellen Sie können auch das Calendar-Objekt selbst auf eine andere Zeitzone umstellen.get(calendar. Datum und Uhrzeit Die get-Methoden des Calendar-Objekts – wie z. calendar. Wenn Sie sich nämlich mit getTime() ein Date-Objekt zurückliefern lassen. Soll DateFormat die Uhrzeit in der Zeitzone des Calendar-Objekts formatieren. mittels setTimeZone() an das Calendar-Objekt: Calendar calendar = Calendar.

die aus Gründen der Abwärtskompatibilität noch unterstützt werden. i < ids. Africa/Dakar Etc/UTC Europe/Berlin. Atlantic/Bermuda America/Buenos_Aires Atlantic/South_Georgia Atlantic/Azores Europe/Dublin. Dazu müssen Sie sich mit TimeZone. .getAvailableIDs(). PST.getAvailableIDs() ein String-Array mit den IDs der auf dem System verfügbaren Zeitzonen zurückliefern lassen und prüfen. String ids[] = TimeZone. ++i) H i n we i s Die weit verbreiteten dreibuchstabigen Zeitzonen-Abkürzungen wie ETC. Canada/Eastern. America/New_York Datum und Uhrzeit Canada/Atlantic.length. dass die zur Erstellung des TimeZone-Objekts benötigten Informationen auf dem aktuellen System vorhanden sind.) Zeitzone Östliche Normalzeit Atlantik Normalzeit Argentinische Zeit South Georgia Normalzeit Azoren Zeit Greenwich Zeit Koordinierte Universalzeit Zentraleuropäische Zeit Osteuropäische Zeit Zentralafrikanische Zeit Israelische Zeit Moskauer Normalzeit Arabische Normalzeit Golf Normalzeit Maledivische Normalzeit Sri Lanka Zeit Indochina Zeit Chinesische Normalzeit Japanische Normalzeit Östliche Normalzeit Salomoninseln Zeit Marshallinseln Zeit entspricht GMT GMT-05:00 GMT-04:00 GMT-03:00 GMT-02:00 GMT-01:00 GMT-00:00 GMT+01:00 GMT+02:00 GMT+03:00 GMT+04:00 GMT+05:00 GMT+06:00 GMT+07:00 GMT+08:00 GMT+09:00 GMT+10:00 GMT+11:00 GMT+12:00 Verfügbare Zeitzonen abfragen Die Übergabe einer korrekten ID ist aber noch keine Garantie. ob die gewünschte ID darin vertreten ist. TimeZone tz = null. sind nicht eindeutig und sollten daher möglichst nicht mehr verwendet werden.166 >> Zeitzone erzeugen ID US/Eastern. Europe/London. for (int i = 0. Etc/GMT-1 Europe/Kiev Africa/Cairo Asia/Jerusalem Europe/Moscow Asia/Baghdad Asia/Dubai Indian/Maledives Asia/Colombo Asia/Bangkok Asia/Shanghai Asia/Tokyo Australia/Canberra Pacific/Guadalcanal Pacific/Majuro Tabelle 22: Zeitzonen (Forts. CET.

i < ids.getTimeZone() eine ungültige ID übergeben. if(tz == null) // Eigene Zeitzone konstruieren tz = new SimpleTimeZone(rawOffset. int endMonth. Bis hierher unterscheidet sich die Methode noch nicht von einem direkten TimeZone.getTimeZone() als ID einen String der Form »GMT-hh:mm« bzw.getTimeZone(ids[i]). int startDayOfWeek. Zuerst prüft sie. indem Sie TimeZone.length. int rawOffset.equals(id)) tz = TimeZone.>> Datum und Uhrzeit if (ids[i]. sondern zieht die ebenfalls als Argumente übergebenen Informationen zu Zeitverschiebung und Sommerzeit hinzu und erzeugt ein eigenes SimpleTimeZone-Objekt. »GMT+hh:mm« übergeben. startTime. Eigene Zeitzonen erzeugen Eigene Zeitzonen erzeugen Sie am einfachsten. endDay. endDayOfWeek. liefert sie nicht die GMZ-Zeitzone zurück. // Ist gewünschte Zeitzone verfügbar? String ids[] = TimeZone. ++i) if (ids[i]. 167 Wenn Sie TimeZone. /** * Hilfsmethode zum Erzeugen einer Zeitzone (TimeZone-Objekt) */ public static TimeZone getTimeZone(String id. return tz.getTimeZone(ids[i]). Dazu müssen Sie nämlich explizit ein Objekt der Klasse SimpleTimeZone erzeugen und deren Konstruktor.getAvailableIDs(). Wenn ja. int dstSavings) { TimeZone tz = null. startMonth. int endDay.getTimeZone()-Aufruf.getTimeZone().getTimeZone() verfolgt eine zweigleisige Strategie. auch noch die Informationen für Beginn und Ende der Sommerzeit übergeben. for (int i = 0. int startDay. int startMonth. TimeZone tz = TimeZone. } Das Start-Programm zu diesem Rezept zeigt den Aufruf von MoreDate. erzeugt sie direkt anhand der ID das gewünschte TimeZone-Objekt. startDay. dass es zu der ID keine passenden Zeitzonen-Informationen gibt. id. dstSavings). wobei hh:mm die Zeitverschiebung in Stunden und Minuten angibt. Die im Folgenden abgedruckte Methode MoreDate. endTime.equals(searchedID)) tz = TimeZone. neben der frei wählbaren ID für die neue Zeitzone. startDayOfWeek. int startTime.getTimeZone("GMT-01:00"). endMonth. erhalten Sie die GreenwichZeitzone (»GMT«) zurück. um sich ein TimeZone-Objekt für »America/Los_Angeles« zurückliefern zu lassen: Datum und Uhrzeit . Sollte die Methode allerdings feststellen. ob die angegebene ID in der Liste der verfügbaren IDs zu finden ist. int endDayOfWeek. int endTime. Allerdings berücksichtigen die erzeugten TimeZone-Objekte dann keine Sommerzeit.

Calendar. bilden durch Subtraktion die Differenz und rechnen das Ergebnis in die gewünschte Einheit um: import java. so dass er gegebenenfalls selbsttätig den Datumsteil anpasst.util. 05:10 Achtung Wenn Sie die Uhrzeit mit Angabe der Zeitzonen ausgeben: SimpleDateFormat sdf = new SimpleDateFormat("dd. 7200000. 30. HH:mm").getTime())). long diff = time2. 1.GregorianCalendar.1970 00:00:00 Uhr. 22.OCTOBER.getTimeInMillis() . dass SimpleDateFormat in diesem Fall in die Berechnung der »Zeitzone« auch die Sommerzeitverschiebung mit einbezieht. müssen Sie darauf achten. 7200000.console(). Dies liegt daran. Calendar calendar = Calendar.APRIL.printf("\t%s\n". TimeZone tz.SUNDAY. Wenn Sie nach obigem Verfahren den zeitlichen Abstand zwischen zwei reinen Uhrzeiten (beispielsweise von 07:00 zu 14:00 oder von 14:00 zu 05:00 am nächsten Tag) so berechnen wollen. für time1 = 01. HH:mm z").2005 22:30 Uhr und time2 = 03. kann es passieren. 60 Differenz zwischen zwei Uhrzeiten berechnen Die Differenz zwischen zwei Uhrzeiten zu berechnen.168 >> Differenz zwischen zwei Uhrzeiten berechnen // aus Start. sdf. 0).SUNDAY. Ausgabe: 04.getInstance(). 4. Datum und Uhrzeit tz = MoreDate. zurückgeben.time1. MMMM yyyy. muss aber nicht im Sinne des Programmierers liegen. GMT. 1980 Minuten) ergeben. -28800000.01. April 2005. die Datumsanteile beim Erzeugen der GregorianCalendar-Objekte korrekt zu setzen – oder Sie erweitern den Algorithmus. // // // // Differenz Differenz Differenz Differenz in in in in Millisekunden Sekunden Minuten Stunden : : : : diff diff/1000 diff/(60*1000) diff/(60*60*1000) Das obige Verfahren berechnet letzten Endes aber keine Differenz zwischen Uhrzeiten. Das heißt. 30. System.setTimeZone(tz). sondern Differenzen zwischen Zeiten (inklusive Datum). dass für selbst definierte Zeitzonen (ID nicht in der Liste der verfügbaren IDs vorhanden) eine falsche Zeitzone angezeigt wird. -1.format(calendar. sdf. 4.2005 7:30 Uhr würde die Berechnung 33 Stunden (bzw. MMMM yyyy. 3600000). ist grundsätzlich recht einfach: Sie lassen sich die beiden Zeiten als Anzahl Millisekunden seit dem 01. wie man ihn am Zifferblatt einer Uhr ablesen würde.getTimeInMillis().05.getTimeZone("America/Los_Angeles".05.java SimpleDateFormat sdf = new SimpleDateFormat("dd. . 2. Calendar. Dies kann. 7. -Calendar. 0). 1. GregorianCalendar time2 = new GregorianCalendar(2005. GregorianCalendar time1 = new GregorianCalendar(2005. Calendar.

util.YEAR). 3. GregorianCalendar time2 = new GregorianCalendar(2005. Dann rechnen Sie diff Modulo 60 und erhalten den Sekundenanteil. ob die reine Uhrzeitdifferenz ohne Berücksichtigung des Datumsanteils (true) oder die Differenz zwischen den vollständigen Datumsangaben (false) berechnet wird. import java. wird die Differenz von time1 zu time2 berechnet.getTimeInMillis() .get(Calendar. Als Ergebnis liefert die Methode ein Objekt ihrer eigenen Klasse zurück.get(Calendar. // // // // Differenz Differenz Differenz Differenz in in in in Millisekunden Sekunden Minuten Stunden : : : : diff diff/1000 diff/(60*1000) diff/(60*60*1000) 61 Differenz zwischen zwei Uhrzeiten in Stunden. Die statische Methode getInstance() der nachfolgend definierten Klasse TimeDiff tut genau dies. Liegt die Uhrzeit von time1 zeitlich nach der Uhrzeit von time2.MONTH). Datum und Uhrzeit Liegt die Uhrzeit von time1 zeitlich vor der Uhrzeit von time2. wird die Differenz von time1 zu time2 am nächsten Tag berechnet. 0).util. Diesen ziehen Sie von der Gesamtzahl ab (wozu Sie am einfachsten die Ganzzahldivision diff/60 durchführen). import java. 7. time2.DAY_OF_MONTH)). Sekunden berechnen Um die Differenz zwischen zwei Uhrzeiten in eine Kombination aus Stunden.get(Calendar.getTimeInMillis(). Minuten und Sekunden umzurechnen. berechnen Sie zuerst die Differenz in Sekunden (diff). erhöhe Tag von time2 if (clone1. 540 Minuten) berechnet. 1). 1.GregorianCalendar. Beispiel: Für time1 = 07:30 Uhr und time2 = 22:30 Uhr werden 15 Stunden (bzw. Minuten und Sekunden gespeichert sind. 30. Analog rechnen Sie den Minutenanteil heraus und behalten die Stunden übrig.add(Calendar. // time1 kopieren und Datumsanteil an time2 angleichen GregorianCalendar clone1 = (GregorianCalendar) time1. .DAY_OF_MONTH.after(time2)) time2. // liegt die Uhrzeit von clone1 hinter time2. 0). 1. 30. Minuten. über das sie steuern können. Sie übernimmt als Argumente die beiden Datumswerte (in Form von Calendar-Objekten) sowie ein optionales boolesches Argument.clone1.set(time2. in dessen public-Feldern die Werte für Stunden. 1. GregorianCalendar time1 = new GregorianCalendar(2005. 900 Minuten) berechnet. time2.clone(). Beispiel: Für time1 = 22:30 Uhr und time2 = 07:30 Uhr werden 9 Stunden (bzw. 22.>> Datum und Uhrzeit 169 Differenz ohne Berücksichtung des Tages Der folgende Algorithmus vergleicht die reinen Uhrzeiten. clone1.Calendar. diff = time2.

YEAR).abs(t2.get(Calendar.DAY_OF_MONTH)). // Berechnet die Zeit zwischen zwei Uhrzeiten. /** * Klasse zur Repräsentation und Berechnung von Zeitabständen * zwischen zwei Uhrzeiten * */ public class TimeDiff { public int hours.after(t2)) t2. false). Datum und Uhrzeit .util. Rezept 60 if (onlyClock) { clone1. } } Listing 61: Die Klasse TimeDiff Minuten und Stunden berechnen (int) (diff%60).Calendar. Minuten. boolean onlyClock) { Calendar clone1 = (Calendar) t1. } diff = Math.get(Calendar. Sekunden berechnen import java. vgl. } // Berechnet die Zeit zwischen zwei Uhrzeiten. diff /= 60.getTimeInMillis() . diff /= 60. (int) diff. public int seconds.getTimeInMillis())/1000. // Sekunden.add(Calendar.minutes = td.set(t2. td. // reine Uhrzeit. gegeben als // Calendar-Objekte (berücksichtigt ganzes Datum) public static TimeDiff getInstance(Calendar t1.MONTH). Calendar t2. public int minutes.seconds = td. wird nur Differenz zwischen // Tageszeiten berechnet) public static TimeDiff getInstance(Calendar t1.hours = return td.get(Calendar. long diff.clone().170 >> Differenz zwischen zwei Uhrzeiten in Stunden.clone1.DAY_OF_MONTH. Calendar t2) { return getInstance(t1. gegeben als // Calendar-Objekte (wenn onlyClock true. Datumsanteil eliminieren. t2. 1). t2. if (clone1. t2. (int) (diff%60). TimeDiff td = new TimeDiff().

GregorianCalendar.getInstance(time1.out. System. Für time1 = 22:30 Uhr und time2 = 07:30 Uhr werden 9 Stunden (bzw.getInstance(time1.out.println(" " + td. Sekunden von der Uhrzeit des ersten Calendar-Objekts bis zur Uhrzeit des zweiten Calendar-Objekts – so wie die Zeitdifferenz auf dem Zifferblatt einer Uhr abzulesen ist: Für time1 = 07:30 Uhr und time2 = 22:30 Uhr werden 15 Stunden (bzw. repräsentiert das zurückgelieferte Objekt die Stunden. true).hours + " h " + td. TimeDiff td.println(). 22.util. Uhrzeiten (mit Datum)\n").out. time2.getTime())).format(time2.seconds + " sec").out.out.out.Calendar.minutes + " min " + td. import java.hours + " h " + td. 3. 2 : " + dfDateTime. 540 Minuten) berechnet Das Start-Programm zu diesem Rezept demonstriert die Verwendung: import java.>> Datum und Uhrzeit 171 Wenn Sie für den dritten Parameter false übergeben oder einfach die überladene Version mit nur zwei Parametern aufrufen. 1 : " + dfDateTime.out. 0). 30. 7.format(time1.println(" Zeit System. 4.println(" " + td. 1. 900 Minuten) berechnet. Datum und Uhrzeit Wenn Sie für den dritten Parameter true übergeben.minutes + " min " + td.println("\n Differenz zw.getTime())). td = TimeDiff.text. public class Start { public static void main(String args[]) { DateFormat dfDateTime = DateFormat. import java. 30. // Reine Uhrzeit System. Uhrzeiten (ohne Datum)\n").println(" Zeit GregorianCalendar(2005.println("\n\n Differenz zw. Sekunden vom ersten Datum zum zweiten.util. 4. System.DateFormat. GregorianCalendar time1 = new GregorianCalendar time2 = new System. } } Listing 62: Einsatz der Klasse TimeDiff . time2).getDateTimeInstance(). repräsentiert das zurückgelieferte Objekt die Differenz in Stunden. GregorianCalendar(2005. // Berücksichtigt Datum System. Minuten. td = TimeDiff. System. 0).seconds + " sec"). Minuten.

Internet etc. System. messen Sie den Algorithmus unbedingt erst ab dem Zeitpunkt. Sekunden 62 Präzise Zeitmessungen (Laufzeitmessungen) Für Zeitmessungen definiert die Klasse System die statischen Methoden currentTimeMillis() und nanoTime(). sondern die der Einleseoperation messen. Wenn Sie einen Algorithmus testen. // Code.nanoTime(). Dateisystem. // 3. Benutzeraktionen sollten ebenfalls vermieden werden.nanoTime(). da die Daten bereits eingelesen sind. Zeitmessungen haben typischerweise folgendes Muster: // 1. dessen Laufzeit gemessen wird Thread. Zeitmessung beginnen (Startzeit abfragen) long start = System.println(" Laufzeit: " + diff). . beachten Sie folgende Punkte: Zugriffe auf Konsole. Zeitmessung auswerten (Differenz bilden und ausgeben) long diff = end-start.out. Minuten. der Daten aus einer Datei oder Datenbank verarbeitet.5 verfügbare Methode nanoTime() vorziehen. Zeitmessung beenden (Endzeit abfragen) long end = System. sondern Ihren Code. Ansonsten kann es passieren. Beide Methoden werden in gleicher Weise eingesetzt und liefern Zeitwerte in Millisekunden (10-3 sec) bzw. dass das Einlesen der Daten weit mehr Zeit benötigt als deren Verarbeitung und Sie folglich nicht die Effizienz Ihres Algorithmus. sollten möglichst vermieden werden. Sie wollen ja nicht die Reaktionszeit des Benutzers messen. Laufzeitmessungen Wenn Sie Laufzeitmessungen durchführen. um die Performance eines Algorithmus oder einer Methode zu testen. Derartige Zugriffe sind oft sehr zeitaufwendig.sleep(50000). In der Praxis werden Sie wegen der größeren Genauigkeit in der Regel die ab JDK-Version 1. // 2. Nanosekunden (10-9 sec).172 >> Präzise Zeitmessungen (Laufzeitmessungen) Datum und Uhrzeit Abbildung 34: Berechnung von Uhrzeitdifferenzen in Stunden.

wenn der Systemzeitgeber. ab. Vorsicht Jitter! Viele Java-Interpreter fallen unter die Kategorie der Just-In-Time-Compiler. Methoden bei der ersten Ausführung von Bytecode in Maschinencode umwandeln. ebenso wie Date() oder Calendar. GMT.B. (Tatsächlich rufen Date() und Calendar. der die Uhrzeit liefert. A c htu n g Nanosekunden in Stunden. Es bietet sich daher an. speichern und bei der nächsten Ausführung dann den bereits vorliegenden Maschinencode ausführen. Führen Sie wiederholte Messungen durch.1970 00:00:00 Uhr.oder Calendar-Objekts zu verwenden. Datum und Uhrzeit currentTimeMillis() und nanoTime() Die Methode currentTimeMillis() gibt es bereits seit dem JDK 1. Die Methode nanoTime() gibt es erst seit dem JDK 1. Messung der Zeit. E x k ur s .>> Datum und Uhrzeit 173 Die Methode currentTimeMillis() eignet sich daher nur für Messungen von Operationen.5.currentTimeMillis() auf.) Die Methode nanoTime() ist die Methode der Wahl für Performance-Messungen.getInstance() intern System. Sie greift. dass die Tests unter denselben Bedingungen und mit denselben Ausgangsdaten durchgeführt werden. können vom Menschen meist nur schwer miteinander verglichen und in ihrer tatsächlichen Größenordnung erfasst werden. die im Bereich von Nanosekunden aktualisiert werden. Sie fragt die Zeit von dem genauestens verfügbaren Systemzeitgeber ab. Wenn Sie verschiedene Algorithmen/Methoden miteinander vergleichen. Millisekunden und Nanosekunden umrechnen Laufzeitunterschiede.) Die von diesen Systemzeitgebern zurückgelieferte Anzahl Nanosekunden muss sich allerdings nicht auf eine feste Zeit beziehen und kann/sollte daher nicht als Zeit/Datum interpretiert werden. Wiederholen Sie die Messungen. Verwenden Sie stets gleiche Ausgangsdaten. achten Sie darauf.0. Zugriff auf Datenbanken oder Internet. die länger als nur einige Millisekunden andauern (Schreiben in eine Datei. beispielsweise in einer Schleife. (Die meisten Rechner besitzen mittlerweile Systemzeitgeber.getInstance() die aktuelle Systemzeit in Millisekunden seit dem 01. Verlassen Sie sich nie auf eine Messung. die ein Benutzer für die Bearbeitung eines Dialogfelds oder Ähnliches benötigt). Sekunden.) Die Genauigkeit von Zeitmessungen mittels currentTimeMillis() ist daher von vornherein auf die Größenordnung von Millisekunden beschränkt. und bilden Sie den Mittelwert. (Versuchen Sie also nicht. in noch längeren Intervallen (etwa alle 10 Millisekunden) aktualisiert wird. Minuten. insofern als sie Codeblöcke wie z. die in Nanosekunden berechnete Differenz vor der Ausgabe in eine Kombination höherer Einheiten umzurechnen. In diesem Fall sollten Sie eine zu beurteilende Methode unbedingt mehrfach ausführen und die erste Laufzeitmessung verwerfen. Sie verschlechtert sich weiter. den Rückgabewert von nanoTime() in Millisekunden umzurechnen und zum Setzen eines Date.01. die in Nanosekunden ausgegeben werden.

seconds + " sec " + td. 63 Uhrzeit einblenden In den bisherigen Rezepten ging es mehr oder weniger immer darum. (time%60).seconds td. time /= 1000. wie der Name schon verrät. Zeitmessung auswerten (Differenz bilden und ausgeben) long diff = end-start. = = = = = (int) (int) (int) (int) (int) (time%1000000).getInstance(diff).millis td. Minuten.hours return td. die. nanos. Als Dank fordert er die Komponente nach jeder Aktualisierung der Uhrzeit auf. (time%1000).. sich neu zu zeichnen. . minutes. übernimmt der ClockThread-Konstruktor eine Referenz auf die Komponente. (time%60).minutes td. In diesem Rezept erfolgen das Abfragen und das Anzeigen der Zeit weitgehend getrennt.out. die Zeit einmalig abzufragen und irgendwie weiterzuverarbeiten. Die Anzeige der Uhr kann in einer beliebigen Swing-Komponente (zurückgehend auf die Basisklasse JComponent) erfolgen. millis. Aufruf: // 3.hours + " h " + td. public static TimeDiff getInstance(long time) { TimeDiff td = new TimeDiff(). } } Listing 63: Die Klasse TimeDiff zerlegt eine Nanosekunden-Angabe in höhere Einheiten.nanos td. von Thread abgeleitet ist und einen eigenständigen Thread repräsentiert. * */ public class TimeDiff { public public public public public int int int int int hours. td. time.nanos + " nano"). time/= 60. seconds. time/= 60. Um die Verbindung zwischen ClockThread und Swing-Komponente herzustellen. Wie aber sieht es aus. Für das Abfragen ist eine Klasse ClockThread verantwortlich.174 >> Uhrzeit einblenden Datum und Uhrzeit /** * Klasse zum Umrechnen von Nanosekunden in Stunden. time /= 1000000. wenn die Uhrzeit als digitale Zeitanzeige in die Oberfläche einer GUI-Anwendung oder eines Applets eingeblendet werden soll? Zur Erzeugung einer Uhr müssen Sie die Zeit kontinuierlich abfragen und ausgeben. td = TimeDiff. System.millis + " milli " + td.minutes + " min " + td..println(" Laufzeit: " + td.

Date.c = c. Datum und Uhrzeit /** * Thread-Klasse. die aktuelle Uhrzeit in Komponenten einblendet * */ public class ClockThread extends Thread { private static String time.repaint(). this. } } } public static String getTime() { return time. . public ClockThread(JComponent c) { this. In der Schleife wird die aktuelle Zeit abgefragt. formatiert und im statischen Feld time gespeichert.util.>> Datum und Uhrzeit 175 import java.swing. Die run()-Methode enthält eine einzige große while-Schleife.start(). private DateFormat df = DateFormat. } } Listing 64: Die Klasse ClockThread Der Konstruktor von ClockThread speichert die Referenz auf die Anzeige-Komponente und startet den Thread.format(new Date()). die so lange durchlaufen wird. } catch(InterruptedException e) { return. private JComponent c. // Komponente zum Neuzeichnen auffordern c. von wo sie die Anzeige-Komponente mit Hilfe der public getTime()-Methode auslesen kann. // eine Sekunde schlafen try { sleep(1000). import javax. woraufhin intern dessen run()-Methode gestartet wird (mehr zu Threads in der Kategorie »Threads«). } public void run() { while(isInterrupted() == false) { // Uhrzeit aktualisieren ClockThread.time = df. import java.getTimeInstance(). wie der Thread ausgeführt wird.text.JComponent.DateFormat.

setSize(500. private ClockPanel display.176 >> Uhrzeit einblenden Das Start-Programm zu diesem Rezept demonstriert.setFont(new Font("Arial".setVisible(true).swing. } public static void main(String args[]) { // Fenster erzeugen und anzeigen Start mw = new Start(). public Start() { setTitle("Fenster mit Uhrzeit"). mw.EXIT_ON_CLOSE). mw.CENTER). setDefaultCloseOperation(JFrame. wie die Uhrzeit mit Hilfe von ClockThread in einem JPanel.event. Font. getContentPane(). 18)).350). import java. g. display = new ClockPanel().setLocation(200. // Uhrzeit einblenden g. .awt. 15. } } private ClockThread ct. import javax.*. angezeigt werden kann.setColor(Color.*.paintComponent(g). } } Listing 65: GUI-Programm mit Uhreinblendung Datum und Uhrzeit Dem Fenster fällt die Aufgabe zu.PLAIN. den Uhrzeit-Thread in Gang zu setzen und mit der AnzeigeKomponente zu verbinden. public class Start extends JFrame { class ClockPanel extends JPanel { public void paintComponent(Graphics g) { super. 30).blue).300). // Thread für Uhrzeit erzeugen und starten ct = new ClockThread(display). g.add(display.getTime().drawString(ClockThread. import java. Beides geschieht im Konstruktor des Fensters bei der Erzeugung des ClockThread-Objekts. hier die ContentPane des Fensters. BorderLayout. mw.*.awt.

die paintComponent()-Methode zu überschreiben und den Code zum Einblenden der Uhrzeit aufzunehmen. Nur so ist es möglich. Datum und Uhrzeit Abbildung 35: GUI-Programm mit eingeblendeter Uhrzeit in JPanel .>> Datum und Uhrzeit 177 Für die Anzeige-Komponente muss eine eigene Klasse (ClockPanel) abgeleitet werden.

.

util. import java.hasMoreElements()) { // Aktuellen Schlüssel auslesen String key = (String)keys.Properties.keys().PrintStream. // Wert auslesen Listing 66: Ausgabe von Umgebungsinformationen . getProperty() der Properties-Instanz genutzt.System 64 Umgebungsvariablen abfragen System Die java.io. Diese kann per while-Schleife durchlaufen werden. import java.keys() lassen sich dann alle Schlüssel in Form einer Enumerator-Instanz auslesen. Mit Hilfe von env.util.getProperties(). da sie in Form einer java. die deshalb noch in einen String gecastet werden muss. wird im folgenden Beispiel der lokalen Variablen env eine Referenz auf die von System. // Schlüssel durchlaufen while(keys. Deren Rückgabe liegt allerdings in Form einer ObjectInstanz vor. bevor sie weiterverwendet werden kann. Diese Informationen können durchlaufen werden. Innerhalb der Schleife kann der aktuelle Schlüssel mit Hilfe der Methode nextElement() der Enumerator-Instanz ermittelt werden.nextElement(). public class EnvInfo { /** * Abfrage und Ausgabe von Umgebungsvariablen */ public static void enumerate() { Jetzt lässt sich der Wert der so ermittelten Systemvariablen auslesen.out. // Schlüssel auslesen Enumeration keys = env.Properties-Instanz vorliegen.System-Klasse stellt über die Methode getProperties() eine elegante Möglichkeit zum Abrufen von Umgebungsinformationen zur Verfügung.getProperties zurückgelieferte Properties-Instanz zugewiesen. Um die Systemvariablen durchlaufen zu können.Enumeration.util.lang. Dazu wird die Methode // Properties einlesen Properties env = System. // Standardausgabe referenzieren PrintStream out = System. import java. der als Parameter der Schlüssel übergeben wird.

Mit dem Schlüssel java. sondern können dies direkt via System.getProperty(key).println(String.getProperty() erledigen. value)).version können die Versionsinformationen von Java ausgelesen werden: public class Start { public static void main(String[] args) { // Betriebssystem-Name auslesen System.name und os.und Java-Versionsinformationen .println( Listing 67: Ermitteln von Betriebssystem.out. key. // Daten ausgeben out.) Abbildung 36: Ausgabe der Umgebungsinformationen 65 H in we is Wenn Sie den Wert eines bestimmten Schlüssels eruieren wollen. Betriebssystem und Java-Version bestimmen Die Bestimmung von Betriebssystem und verwendeter Java-Version erfolgt mit Hilfe der Schlüssel os.getProperty() gehen. } } System } Listing 66: Ausgabe von Umgebungsinformationen (Forts. müssen Sie nicht den Umweg über System.format("%s = %s".version.getProperties().180 >> Betriebssystem und Java-Version bestimmen String value = env.

language Garantiert nein ja nein ja nein ja nein Beschreibung Kürzel des Landes. System. // Java-Version auslesen System.format("Version: %s".version"))).out.und Java-Versionsinformationen (Forts.format("OS: %s".getProperty() unter Verwendung der folgenden Schlüssel abgerufen werden: Schlüssel user. System.getProperty("os.home user.name"))).out. die der Nutzer aktiviert hat – beispielsweise de für Deutsch oder en für Englisch Tabelle 23: Schlüssel für den Abruf von Benutzerinformationen System .version"))).getProperty("os. das der Nutzer in den Systemeinstellungen angegeben hat – beispielsweise DE für Deutschland oder AT für Österreich Aktuelles Arbeitsverzeichnis Verwendete Variante der Länder. } } Listing 67: Ermitteln von Betriebssystem.println( String. Diese können per System.timezone user.dir user.getProperty("java.>> System 181 String.country user. // Betriebssystem-Version auslesen System.name user.println( String.variant user.und Spracheinstellungen Home-Verzeichnis des Nutzers (bei Windows beispielsweise der Ordner »Eigene Dateien«) Verwendete Zeitzone Anmeldename des Nutzers Kürzel der Sprache.) Abbildung 37: Ausgabe von Java.format("Java-Version: %s".und Betriebssystem-Version 66 Informationen zum aktuellen Benutzer ermitteln Die Java-Runtime stellt einige Informationen zum aktuellen Benutzer zur Verfügung. System.

home java. die durchsucht werden.class.dir.separator path.vm.ext.home user. Nicht alle dieser Variablen sind auf jedem System verfügbar.class.tmpdir java.version java.vm. wenn Java-Extensions geladen werden sollen Name des Betriebssystems Prozessor-Architektur Version des Betriebssystems Trenner zwischen Pfaden und Verzeichnissen Trenner zwischen mehreren Pfaden Zeilenumbruch-Zeichenfolge (»\n« bei Unix.vendor. aber Java sichert die Existenz zumindest einiger Umgebungsvariablen zu: Schlüssel java. die durchsucht werden.home und user.getProperty() abgerufen werden können.separator line.182 >> Zugesicherte Umgebungsvariablen Die nicht garantierten Elemente sind nicht auf jedem System vorhanden.version java.path java.url java.separator user.name werden aber in jedem Fall einen Wert zurückgeben.vm.vendor java.specification.specification. 67 System Zugesicherte Umgebungsvariablen Java stellt eine große Anzahl Umgebungsvariablen bereit.library.vm. da sie zu den zugesicherten Systeminformationen gehören.dirs os.name java.name user.name os.version java.specification.version java.version file.specification.io.specification. user. wenn Java-Libraries geladen werden sollen Temporäres Verzeichnis Compiler-Name Pfade.compiler java. die über System.version java.dir Beschreibung Java-Version Anbieter Anbieter-Homepage Installationsverzeichnis Version der JVM-Spezifikation Anbieter der JVM-Spezifikation Name der JVM-Spezifikation JVM-Version JVM-Anbieter JVM-Name JRE-Version JRE-Anbieter JRE-Spezifikation Java Class Format-Version Klassenpfad Pfade.vm.vendor java.name java.specification. Die drei Schlüssel user.path java. »\r\n« bei Windows) Anmeldename des aktuellen Benutzers Home-Verzeichnis des aktuellen Benutzers Aktuelles Arbeitsverzeichnis Tabelle 24: Zugesicherte Systemvariablen .vendor java.name java.vendor java.vm.arch os.

next(). if(null != value && value.util. // Wert abrufen String value = env.length() > 0) { System.util. key.Iterator. So kann beispielsweise das Home-Verzeichnis des aktuell angemeldeten Benutzers ausgelesen oder die PATH-Angabe interpretiert werden.keySet(). den Sie mit Hilfe von System.getenv(). A cht un g Wenn Sie andere Java-Umgebungsvariablen als die zugesicherten verwenden wollen. zum Benutzer. immer hinterfragen und auf null prüfen.iterator(). Listing 68: Ausgabe aller Umgebungsvariablen des Betriebssystems System .out.hasNext()) { // Schlüssel abrufen String key = keys. import java. String> einlesen Map<String. sollten Sie die Existenz eines Werts. auf die Umgebungsvariablen des Betriebssystems zuzugreifen.>> System 183 Interessant für den produktiven Einsatz dürften die Informationen zum Betriebssystem.getenv() eine Referenz auf die Systemvariablen zugewiesen wird. bevor Sie ihn verwenden: String value = System. public class SystemInfo { /** * Abfrage und Ausgabe von Systemvariablen */ public static void enumerate() { // Systemvariablen in Map<String.println(String. die für Schlüssel und Werte nur Strings zulässt und der mit System. etwa zur JRE. // Iteratur durchlaufen while(keys.Iterator können die Schlüssel durchlaufen werden. Das Auslesen dieser Informationen geschieht mit Hilfe einer java. werden in der Praxis nicht allzu häufig benötigt.get(key). um die Schlüssel durchlaufen // zu können Iterator<String> keys = env.getProperty() ermitteln. // Iterator erzeugen.format("Wert von %s: %s".Map.Map-Collection. Andere Informationen.util.getProperty(key). Die Methode get() der Map-Instanz env erlaubt unter Übergabe des Schlüssels als Parameter den Abruf des referenzierten Werts: import java.oder JVM-Version.util. Mittels java. zu den Pfaden und möglicherweise auch die Java-Version sein. value)). String>env = System. } 68 System-Umgebungsinformationen abrufen Seit Java 5 besteht die Möglichkeit.

out.out. die als Parameter zwei String-Werte entgegennimmt.getProperty("Name"))).util. System. . Zuweisen und Abrufen von Werten Die Zuweisung von Werten geschieht mit Hilfe der Methode setProperty().Properties(defaults).format("First name: %s". Als Parameter übergeben Sie den Schlüssel: System. falls der Schlüssel nicht existiert: System.) 69 INI-Dateien lesen Zum Lesen und Schreiben von Konfigurationsdaten und Benutzereinstellungen kann die java.out.println(String. "Germany"))). props. "Musterstadt").Properties props = new java. die ihrerseits weitestgehend der java. überladenen Form kann als zweiter Parameter ein Default-Wert übergeben werden. props. Der erste Parameter dient dabei als Schlüssel. der zurückgeliefert wird.Properties(). Erzeugen mit Standardwerten bzw. der zweite Parameter stellt den Wert dar: props.util. props. props. wodurch sie sich besonders für die Sicherung von Anwendungseinstellungen in Form von INI-Dateien oder XML eignet.Hashtable-Klasse.println(String. Zum Abrufen der gespeicherten Werte verwenden Sie getProperty(). Dabei handelt es sich um eine nicht Genericsfähige Ableitung der java.format("%s = %s".setProperty("FirstName".format("Name: %s". System. eine bereits existierende Properties-Instanz für die Definition von Standardwerten zu verwenden: java.Properties props = new java.out.util. value)).setProperty("City". ohne Standardwerte Properties-Instanzen können mit dem Standardkonstruktor erzeugt werden: java. "Paul").Properties-Klasse verwendet werden.format("Country: %s". Die Properties-Klasse verwaltet Name/Wert-Paare und bietet besondere Methoden zum Laden und Speichern der in ihr enthaltenen Werte.getProperty("FirstName"))).getProperty("City"))).println(String.util.util. props. "Mueller").util. } } } System Listing 68: Ausgabe aller Umgebungsvariablen des Betriebssystems (Forts.out. props.println(String. key. Hashmap-Klasse entspricht. Einer zweiten.format("City: %s".getProperty("Country".setProperty("Name".println(String. Ein überladener Konstruktor erlaubt es.util.184 >> INI-Dateien lesen // Schlüssel und Wert ausgeben System.

props.println( String.format("Name: %s".out..out..util.close().getProperty("FirstName"))).getProperty("Name"))). props.format("First name: %s". // Daten laden props. Dieser wird als Parameter der load()-Methode einer zuvor erzeugten Properties-Instanz übergeben: import java.FileInputStream.load(in).out. System. } } Listing 69: Laden von Properties System .IOException. import java. // Aufräumen in.Properties.getProperty("City"))). System.ini"). try { // FileInputStream-Instanz zum Laden der Daten FileInputStream in = new FileInputStream(filename).format("City: %s".println( String.io. import java.>> System 185 Gespeicherte Properties laden Das Laden einer INI-Datei erfolgt mit Hilfe eines InputStreams. } public static void main(String[] args) { // Daten laden Properties props = load("app.println( String. props. } catch (IOException e) { // Eventuell aufgetretene Ausnahmen abfangen } // Ergebnis zurückgeben return props. // .und ausgeben System.io. public class Start { /** * Lädt die in der angegebenen Datei gespeicherten Parameter */ private static Properties load(String filename) { // Properties-Instanz erzeugen Properties props = new Properties().

System.InputStream-Instanz als Parameter übergeben. System.out.und ausgeben System. props.format("Name: %s".println( String.getProperty("Name"))). Dieser Methode wird eine java. try { // FileInputStream-Instanz zum Laden der Daten FileInputStream in = new FileInputStream(filename).out.close(). } catch (IOException e) { // Eventuell aufgetretene Ausnahmen abfangen } finally { // Aufräumen in.util.io. import java.io. import java.println( Listing 70: Laden von als XML vorliegenden Daten .io. props.format("First name: %s". public class Start { /** * Lädt die in der angegebenen XML-Datei gespeicherten Parameter */ private static Properties load(String filename) { // Properties-Instanz erzeugen Properties props = new Properties(). } public static void main(String[] args) { // Daten laden Properties props = load("app. mit deren Hilfe die Daten geladen werden: System import java.loadFromXML(in).FileInputStream. Einziger Unterschied ist die verwendete Methode: Statt load() wird hier loadFromXML() verwendet. } // Ergebnis zurückgeben return props.186 >> INI-Dateien lesen Gespeicherte Properties im XML-Format laden Analog zum Laden von in Textform vorliegenden Einstellungen gestaltet sich das Laden der Daten aus einer XML-Datei.. // Daten laden props.getProperty("FirstName"))).Properties.out..println( String.IOException. // .xml").

// Speichern props.) 70 INI-Dateien schreiben Zum Speichern einer Properties-Instanz kann deren Methode store() verwendet werden. props. } } Listing 71: Speichern einer Properties-Instanz System . } } Listing 70: Laden von als XML vorliegenden Daten (Forts.ini".IOException.Properties.>> System 187 String.setProperty("Name".io. "Hans"). String comment) { try { // FileOutputStream-Instanz zum Speichern instanzieren FileOutputStream fos = new FileOutputStream(path). String path.close().setProperty("FirstName". props. public class Start { /** * Speichert eine Properties-Datei unter dem angegebenen Namen */ public static void save( Properties props.format("City: %s". "Saved data").store(fos. Dabei kann eine IOException auftreten. "data.io.util. "Musterstadt"). weshalb das Speichern in einen try-catch-Block gefasst werden sollte: import java. // Speichern save(props. // Werte setzen props.setProperty("City". import java.FileOutputStream. comment). import java. props. // Aufräumen fos. } catch (IOException ignored) {} } public static void main(String[] args) { // Properties-Instanz erzeugen Properties props = new Properties(). "Mustermann").getProperty("City"))).

// Aufräumen fos. "Mustermann"). Auch hier kann es zu einer IOException kommen (etwa wenn auf die Datei nicht schreibend zugegriffen werden konnte). public class Start { /** * Speichert eine Properties-Datei unter dem angegebenen Namen * als XML */ public static void saveAsXml( Properties props. // Werte setzen props. 71 System INI-Dateien im XML-Format schreiben Das Speichern von INI-Dateien im XML-Format erfolgt analog zum Speichern im Textformat.io.util. String comment) { try { // FileOutputStream-Instanz zum Speichern instanzieren FileOutputStream fos = new FileOutputStream(path).IOException. String path.storeToXML(fos. props.setProperty("FirstName".FileOutputStream. jedoch wird statt der Methode store() die Methode storeToXML() verwendet.io.setProperty("City". import java.close().setProperty("Name". "Hans"). // Als XML Speichern saveAsXml(props. } } Listing 72: Speichern einer INI-Datei als XML . "Saved data"). comment). die mit Hilfe eines try-catch-Blocks abgefangen werden sollte: import java. "Musterstadt"). } catch (IOException ignored) {} } public static void main(String[] args) { // Properties-Instanz erzeugen Properties props = new Properties(). "data.xml".Properties.188 >> INI-Dateien im XML-Format schreiben Der zweite Parameter der überladenen Methode store() dient der Speicherung eines Kommentars – hier könnte beispielsweise auch ein Datum ausgegeben werden. props. import java. // Speichern props.

bis der Prozess abgeschlossen ist p.exec(String. try { // Prozess erzeugen // Syntax für Unix-Systeme: "ping -c 4 -i 1 <Adresse>" // Syntax für Windows-Systeme: "ping <Adresse>" Process p = r.Process-Klasse.waitFor().>> System 189 72 Externe Programme ausführen Externe Prozesse werden mit Hilfe der Methode exec() der java. // Runtime-Instanz erzeugen Runtime r = Runtime. run anywhere« ist beim Ausführen externer Prozesse nicht mehr gegeben. Alternativ – und das wird in der Praxis häufiger vorkommen – kann auf die Beendigung eines Prozesses mit waitFor() gewartet werden. Listing 73: Ping auf einen externen Host System . falls Rechte aus dem aktuellen Kontext heraus fehlen.io. address)). Deren Rückgabe ist eine Instanz der java.io. Das eigentliche Auslesen erfolgt mit Hilfe einer java. Auf diese Weise kann auch gleich sichergestellt werden. import java.Runtime-Klasse gestartet. PrintWriter out = null. Sinnvollerweise wird dies in einer java.io.lang. BufferedReader rdr = null.BufferedReaderInstanz. dass Umlaute korrekt eingelesen werden: Der Konstruktor des InputStreamReaders erlaubt zu diesem und anderen Zwecken die Angabe des zu verwendenden Zeichensatzes.lang.InputStreamReader die im BufferedInputStream vorliegenden Daten verarbeitet. A cht un g Das Starten von externen Prozessen ist sehr stark plattformabhängig und verstößt somit gegen einen der Java-Grundsätze: »Write once.*. das angegebene Programm nicht gefunden werden konnte oder sonstige Fehler bei dessen Ausführung aufgetreten sind. Der Rückgabecode eines Prozesses lässt sich mit Hilfe der Methode exitValue() abrufen.io. // Warten.getRuntime().InputStreamInstanz abgerufen werden. Bei der Ausführung von Prozessen kann es zu IOExceptions kommen. public class Ping { /** * Pingt die angegebene Adresse an */ public static String ping(String address) { StringBuffer result = new StringBuffer(). Die Ausgabe kann über die Methode getInputStream() in Form einer java.io. die per zugrunde liegendem java.BufferedInputStreamInstanz gekapselt.format("ping %s".

190 >> Externe Programme ausführen // BufferedReader erzeugen. um Umlaute korrekt verarbeiten können rdr = new BufferedReader( new InputStreamReader(new BufferedInputStream( p.toString().) Für die Ausgabe der zurückgelieferten Prozessdaten auf die Konsole bietet sich die printf()Methode der Klasse Console an.close().*.io. while(null != (line = rdr.com".length() > 0) { result.length > 0) { address = args[0]. public class Start { public static void main(String[] args) { // Anzupingende Adresse ist erster Parameter String address = "java. } } } catch (IOException e) { // IOException abfangen e.readLine())) { if(line. Dann müssen Sie sich um eventuell enthaltene Umlaute keine Sorgen machen.printStackTrace(). if(args != null && args. } catch (InterruptedException e) { // Ausführung wurde unterbrochen e. } catch (IOException e) {} } return result. "cp850")).getInputStream()). der die Daten einliest // Die Angabe der CodePage ist auf Windows-Systemen // nötig. } } Listing 73: Ping auf einen externen Host (Forts.printStackTrace(). System // Daten einlesen String line = null. import java.append(line + "\r\n"). } // Pingen Listing 74: Ausführen eines externen Prozesses und Ausgeben der vom Prozess zurückgelieferten Daten . } finally { try { // Aufräumen rdr.sun.

freeMemory(). Die Instanz kann über die statische Methode Runtime. Diese Information liefert die Methode freeMemory() einer Runtime-Instanz.console().ping(address). Abbildung 38: Ausführen eines Pings aus Java heraus 73 Verfügbaren Speicher abfragen Mit Hilfe der java.) System Beim Ausführen der Klasse können Sie als Parameter einen Server-Namen oder eine IPAdresse übergeben. } } Listing 74: Ausführen eines externen Prozesses und Ausgeben der vom Prozess zurückgelieferten Daten (Forts.printf("%s%n".>> System 191 String output = Ping.lang. // In MBytes umrechnen Listing 75: Ermitteln des freien Speichers einer Java-Instanz . wie viel Speicher der aktuellen Java-Instanz zur Verfügung steht. // Prozessdaten ausgeben System.getRuntime(). output). // Freien Speicher auslesen long mem = r.getRuntime() referenziert werden: public class Start { public static void main(String[] args) { // Runtime-Instanz referenzieren Runtime r = Runtime. // In KBytes umrechnen double kBytes = ((mem / 1024) * 100) / 100.Runtime-Klasse lässt sich ermitteln.

". . Maximaler Speicher für die Ausführung der Anwendung: -Xmx2048: maximaler Speicher von 2 Kbyte -Xmx300k: maximaler Speicher von 300 Kbyte -Xmx128m: maximaler Speicher von 128 Mbyte Der Standardwert beträgt je nach Systemumgebung und Java-Version zwischen 16 und 64 Mbyte.println( String.) System Die ebenfalls verfügbaren Methoden maxMemory() und totalMemory() können genutzt werden. -Xmx<Größe> Tabelle 25: Kommandozeilen-Parameter für die Reservierung von Speicher 75 DLLs laden Das Laden und Ausführen externer Bibliotheken erfolgt via JNI (Java Native Interface). ihre Methoden werden aus Sicht der Klasse wie gewöhnliche Instanz-Methoden verwendet. 74 Speicher für JVM reservieren Die Reservierung von Speicher für die JVM erfolgt beim Aufruf des Java-Interpreters unter Angabe der Parameter -Xms und -Xmx. um den maximal verfügbaren Speicher und die Gesamtmenge an Speicher der Java-Anwendung auszuwerten. // Ausgeben System. Die Parameter haben dabei folgende Bedeutung: Parameter -Xms<Größe> Bedeutung Anfänglicher Speicher für die Ausführung der Anwendung. mBytes)).out. Dabei wird die externe Bibliothek mit Hilfe von loadLibrary() in eine Java-Klasse eingebunden.192 >> Speicher für JVM reservieren double mBytes = ((kBytes / 1024) * 100) /100. Kilobyte oder Megabyte angegeben werden: -Xms1024: anfänglicher Speicher von 1 Kbyte -Xms100k: anfänglicher Speicher von 100 Kbyte -Xms32m: anfänglicher Speicher von 32 Mbyte Der Standardwert von -Xms ist plattformabhängig und beträgt je nach Systemumgebung und Java-Version 1-2 Mbyte. %g MByte) freien Speicher. kBytes. mem. Der Parameter <Größe> kann dabei in Byte. } } Listing 75: Ermitteln des freien Speichers einer Java-Instanz (Forts.format( "Diese Java-Instanz hat %d Byte " + "(=%g KByte bzw.

out. die die zu implementierenden Funktionen für C. } // Deklaration der Methode in der externen Bibliothek public native String sayHello().. um eine externe Bibliothek zu erstellen.h Start Die so erzeugte Header-Datei kann nun verwendet werden. die die zu implementierenden Funktionen definiert und die externe Bibliothek lädt Kompilieren der Java-Klasse Erzeugen einer C/C++-Header-Datei. das sich im /bin-Verzeichnis der JDK-Installation befindet. public static void main(String[] args) { // Neue Instanz erzeugen Start instance = new Start().loadLibrary("HelloWorld"). // Externe Methode ausführen System.>> System 193 Erstellen einer Java-Klasse.sayHello()). } } Listing 76: Laden und Verwenden einer externen Bibliothek Nach dem Kompilieren der Klasse kann eine C/C++-Header-Datei erzeugt werden. denn es hebt die Plattformund Systemunabhängigkeit von Java auf. . Die Angabe der Dateiendung der externen Bibliothek unterbleibt dabei.h wie folgt aus: javah -jni -classpath "%CLASSPATH%. in der die zu implementierende JNI-Methode definiert ist." -o HelloWorld. Dies geschieht unter Verwendung des Hilfsprogramms javah. Der Aufruf von javah sieht zur Generierung einer Datei HelloWorld. so dass hier eine gewisse Portabilität gewahrt bleibt: public class Start { // Laden der externen Bibliothek static { System.println(instance. Diese wird in der Regel meist nur eine Wrapper-Funktion haben und somit den System Die grundsätzliche Vorgehensweise für den Einsatz von JNI sieht so aus: A c h tun g Das Laden und Verwenden externer Bibliotheken (auf Windows-Systemen meist als DLLs vorliegend) sollte mit Bedacht vorgenommen werden.oder C++-Programme definiert Implementieren der Funktionen in C oder C++ Bereitstellen der externen Bibliothek Das Laden und Verwenden der externen Bibliothek geschieht mit Hilfe eines statischen Blocks und unter Verwendung des Schlüsselworts native.

h . Zunächst soll ein neues C++-Projekt im Visual Studio . System Am Beispiel eines Visual C++-Projekts soll aufgezeigt werden.dll)« festgelegt werden. Nach dem Erzeugen des Projekts müssen unter EXTRAS/OPTIONEN/PROJEKTE/VC++-VERZEICHNISSE die Ordner %JAVA-HOME%/include und %JAVA_HOME%/include/win32 für IncludeDateien hinzugefügt werden: Abbildung 39: Hinzufügen der Java-Include-Verzeichnisse zum Projekt In den Projekteigenschaften müssen unter dem Punkt ALLGEMEIN folgende Einstellungen vorgenommen werden: Verwendung von ATL: Dynamische Verknüpfung zu ATL Zeichensatz: Unicode Unter dem Punkt LINKER muss die Bibliothek %JAVA_HOME%/lib/jawt. jobject). Dieses Projekt ist vom Typ Windows-32-Applikation. wie die Umsetzung stattfinden kann. die implementiert werden muss: JNIEXPORT jstring JNICALL Java_Start_sayHello(JNIEnv *. Innerhalb der Header-Datei ist eine Funktion definiert.194 >> DLLs laden Zugriff auf andere Bibliotheken oder Systemfunktionen erlauben. Die Wrapper-Klasse HelloWorld kapselt den Zugriff auf die eigentlich verwendete Klasse SayHello. In den Projekteigenschaften muss als Ausgabetyp »Dynamische Bibliothek (.NET angelegt werden. Analog kann auch bei Verwendung eines anderen Entwicklungstools und eines anderen Compilers vorgegangen werden. die folgende Header-Definition besitzt: class SayHello { public: Listing 77: SayHello.lib hinzugefügt werden.

h" #include "SayHello.cpp : Wrapper-Klasse.>> System 195 SayHello(void).h> // Default-Einstiegspunkt BOOL APIENTRY DllMain(HANDLE hModule. Listing 77: SayHello.cpp System . }. wird von Java aufgerufen // Includes #include "stdafx. } Listing 78: SayHello.h" // Konstruktor SayHello::SayHello(void) {} // Destruktor SayHello::~SayHello(void) {} // Implementierung von execute char* SayHello::execute() { return "Hello world from C++!". LPVOID lpReserved) { return TRUE.h" #include <win32\jawt_md.h" #include ".h referenzieren und die dort definierte Methode Java_Start_sayHello() implementieren: // HelloWorld. DWORD ul_reason_for_call. } // Implementierung der Java-Methode JNIEXPORT jstring JNICALL Java_Start_sayHello ( JNIEnv *env.) Die Implementierung ist in diesem Fall trivial: #include "StdAfx.h" #include "HelloWorld.cpp Die Wrapper-Klasse HelloWorld. jobject obj) { // Instanz erstellen SayHello *instance = new SayHello().\sayhello.h (Forts. ~SayHello(void).cpp muss die von Java generierte Header-Datei HelloWorld. char* execute(void). Listing 79: JNI-Wrapper-Implementierung HelloWorld.

// Zurückgeben return result. dass einzelne Zeichen in ihre JNI-jchar-Pendants gecastet werden müssen. // Kopieren for(size_t i = 0. da sich sowohl Größe als auch Kodierung der repräsentierten Java-Datentypen deutlich von ihren C++-Pendants unterscheiden. i < size. um gefunden zu werden: Abbildung 40: Verwenden einer C++-Methode aus Java heraus . // Größe bestimmen size_t size = strlen(tmp). // Speicher reservieren memset(jc. Nach dem Kompilieren kann die Bibliothek verwendet werden. nicht mehr benötigte Ressourcen wieder freizugeben. A c h t u ng Achten Sie darauf. die die C-/C++-Gegenstücke zu den Java-Datentypen darstellen. // Aufräumen delete [] jc. } Listing 79: JNI-Wrapper-Implementierung HelloWorld. um keine Speicherlöcher zu erzeugen. Dabei muss sie sich innerhalb eines durch die Umgebungsvariable PATH definierten Pfads befinden. jsize(size)). } // Rückgabe erzeugen jstring result = (jstring)env->NewString(jc. sizeof(jchar) * size).cpp (Forts.) JNI definiert einige Datentypen. Die hier praktizierte Rückgabe von Zeichenketten erfordert beispielsweise. C-/C++-Datentypen müssen stets aus und in die JNI-Datentypen gecastet werden.196 >> DLLs laden // Rückgabe abrufen const char* tmp = instance->execute(). i++) { jc[i] = tmp[i]. System // jchar-Array erzeugen jchar* jc = new jchar[size]. 0.

dass das System insbesondere bei lang laufenden Schleifen die Möglichkeit erhält.util.util. } // Aktuelle Uhrzeit ausgeben System.util.out.out. public class Start { public static void main(String[] args) { // Aktuelle Uhrzeit ausgeben System.printStackTrace().util. } } Listing 80: Pausieren eines Threads per Thread.Date. new Date(). In diesem Fall wird eine InterruptedException geworfen. } catch (InterruptedException e) { e. Während ein Thread per Thread. import java.println( String. // Thread pausieren try { Thread. Klassen.toString())).DateFormat.toString())).Thread können Sie den aktuellen Thread für die als Parameter angegebene Zeit in Millisekunden anhalten.>> System 197 76 Programm für eine bestimmte Zeit anhalten Mit Hilfe der statischen Methode sleep() der Klasse java. die regelmäßig eingebunden werden sollen.text. dass er beendet oder sonstwie unterbrochen wird.Timer-Klasse können Aufgaben wiederholt ausgeführt werden.Calendar.util.println( String.sleep() pausiert.lang. müssen von der Basisklasse java. new Date(). /** Listing 81: Die Klasse SimpleTimerTask definiert einen per Timer auszuführenden Task. kann es vorkommen. So kann dafür gesorgt werden.format("Current time: %s". andere anstehende Aufgaben abzuarbeiten.format("Current time: %s".TimerTask.sleep(5000).TimerTask erben und deren run()-Methode überschreiben: import java. .sleep() System 77 Timer verwenden Mit Hilfe der java. import java. die aufgefangen oder deklariert werden muss: import java. public class SimpleTimerTask extends TimerTask { private boolean running = false.

// Task planen timer. public class Start { public static void main(String[] args) { // TimerTask instanzieren SimpleTimerTask task = new SimpleTimerTask(). Die Angabe von Startverzögerung und Ausführungsintervall erfolgt dabei stets in Millisekunden: import java. Weiterhin kann angegeben werden.getInstance().println(String.schedule(task. A c h t u ng . } } Listing 81: Die Klasse SimpleTimerTask definiert einen per Timer auszuführenden Task.Timer.198 >> Timer verwenden * Wird vom Timer regelmäßig aufgerufen und ausgeführt */ public void run() { String date = DateFormat. // Timer instanzieren Timer timer = new Timer(). System String message = running ? "%s: Still running (%s)" : "%s: Started! (%s)". was er intern auch ist. // Ausgeben einer Nachricht mit der aktuellen Uhrzeit System.) Das Einbinden eines TimerTask geschieht über eine neue Timer-Instanz.util.getTime()).getTimeInstance(). "SimpleTimerTask". (Forts. running = true. deren schedule()Methode eine Instanz der TimerTask-Ableitung als Parameter übergeben wird. dass die Ausführung des Programms so lange fortgesetzt wird.format( Calendar. } } Listing 82: Ausführen eines Timers Beachten Sie. date)).format( message. Nach dem Planen des Tasks kann allerdings mit anderen Aufgaben fortgefahren werden – der Timer verhält sich wie ein eigenständiger Thread. 0. 5000). wann oder mit welcher Startverzögerung und in welchem Abstand die Ausführung stattfinden soll. wie der Timer aktiv ist.out.

Es findet keine Wiederholung der Ausführung statt. Es findet keine Wiederholung der Ausführung statt.>> System 199 Abbildung 41: Ausführung des Timers Neben der hier gezeigten Variante existieren noch weitere Überladungen der schedule()Methode: Überladung schedule(TimerTask task. long period) Beschreibung Führt den Task zur angegebenen Zeit aus. die die Angabe einer Startverzögerung oder einer Startzeit sowie eines Ausführungsintervalls in Millisekunden erlauben: System . Die Verwendung von scheduleAtFixedRate() unterscheidet sich nicht wesentlich von der der schedule()-Methode. long delay) Tabelle 26: Weitere Überladungen der schedule()-Methode 78 TimerTasks gesichert regelmäßig ausführen Wenn TimerTasks zwingend in bestimmten Abständen ausgeführt werden müssen (etwa. ab und sorgt für eine Ausführung des TimerTasks basierend auf der Systemuhrzeit – die natürlich ihrerseits genau sein sollte. wie sie etwa durch den GarbageCollector entstehen können. wenn exakt alle 60 Minuten ein Stunden-Signal ertönen soll). kann dies mit Hilfe der Methode scheduleAtFixedRate() erreicht werden. Date firstTime. Date time) schedule(TimerTask task. Diese Methode fängt Verzögerungen. Es existieren hier lediglich zwei Überladungen. Führt den Task nach der in Millisekunden angegebenen Zeitspanne aus. schedule(TimerTask task. Führt den Task zur angegebenen Zeit aus und wartet vor einer erneuten Ausführung die in period angegebene Zeitspanne in Millisekunden ab.

public class Start { public static void main(String[] args) { // TimerTask instanzieren SimpleTimerTask task = new SimpleTimerTask(). } catch (InterruptedException e) {} } } Listing 84: Dämon-Timer .scheduleAtFixedRate(task.util.sleep(10000). dass die Anwendung. in der sie laufen. // Task planen timer.Timer. Dies kann zu unerwünschten Zuständen führen. beendet werden kann. 5000). solange der Timer noch aktiv ist.Timer. } } Listing 83: Ausführen eines Timers.util. public class Start { public static void main(String[] args) { // TimerTask instanzieren SimpleTimerTask task = new SimpleTimerTask(). 1000).200 >> Nicht blockierender Timer import java. // Task planen timer. 0. der exakt alle fünf Sekunden läuft System 79 Nicht blockierender Timer Standardmäßig sind Timer blockierend: Sie verhindern. sobald die Anwendung beendet werden soll. Dies kann durch Übergabe des Werts true an den Konstruktor der Timer-Instanz erreicht werden: import java. // Timer als Dämon instanzieren Timer timer = new Timer(true). // Timer instanzieren Timer timer = new Timer(). 0. Um einen Timer automatisch zu beenden. // Anwendung nach zehn Sekunden beenden try { Thread.scheduleAtFixedRate(task. muss er als Dämon-Timer ausgeführt werden.

// Anwendung zehn Sekunden pausieren try { Thread. HKEY_LOCAL_MACHINE\Software\JavaSoft\Prefs repräsentieren. Unterhalb dieser Einträge . 0.util. 1000). Das Paket java. // Task planen timer.util. darauf zuzugreifen: Das Paket java. Der volle Zugriff auf die Registry ist hiermit allerdings nicht möglich. // Timer instanzieren Timer timer = new Timer().Timer. Dies erfordert den Einsatz des Java Native Interface (JNI). } catch (InterruptedException e) {} // Timer beenden timer.scheduleAtFixedRate(task. die alle noch anstehenden und nicht bereits ausführenden Aufgaben abbricht und den Timer beendet: import java. Einsatz der Windows API für den Zugriff auf die entsprechenden Registry-Funktionen. dateibasierte Datenbank zur Registrierung von anwendungsspezifischen Werten.>> System 201 80 Timer beenden Timer beenden sich in der Regel.sleep(10000). public class Start { public static void main(String[] args) { // TimerTask instanzieren SimpleTimerTask task = new SimpleTimerTask(). Dies kann je nach Programmierung einige Zeit dauern oder bei endlos laufenden Timern schier unmöglich sein. wenn die letzte Referenz auf den Timer entfernt und alle ausstehenden Aufgaben ausgeführt worden sind. System Aus diesem Grund verfügt die Klasse Timer über die Methode cancel(). welche die speziellen Registry-Schlüssel HKEY_CURRENT_USER\Software\JavaSoft\Prefs bzw. die als Registry-Ersatz dient). } } Listing 85: Beenden eines Timers über seine cancel()-Methode 81 Auf die Windows-Registry zugreifen Die berüchtigte Windows-Registry ist eine simple. Dafür funktioniert dieser Ansatz auch unter Unix/Linux (wobei eine XML-Datei erzeugt wird.prefs bietet Klassen und Methoden zum Setzen und Lesen von Einträgen in einem Teilbaum der Registry.prefs In diesem Paket bietet die Klasse Preferences die statischen Methoden userNode() und systemNode().util. Aus einem Java-Programm heraus hat man zwei Möglichkeiten.cancel().

*.println(keys[i] + " : " + userPrefs. mit C-Code herumzuhantieren.get(keys[i]. i++) System. putBoolean(). i < keys. z. so dass man nicht gezwungen ist.net/projects/jregistrykey/ das Binary-Package jRegistryKey-bin. System public class Start { public static void main(String[] args) { try { // Knoten anlegen im Teilbaum HKEY_CURRENT_USER Preferences userPrefs = Preferences. das in der PATH-Umgebungsvariable definiert ist. "")). andere Bereiche der Windows-Registry zu lesen oder zu verändern.3) und gegebenenfalls die Dokumentation herunter. Dies erfolgt wie bei einer Hashtabelle mit den Methoden put() und get().prefs erlaubt wie bereits erwähnt nur das Ablegen oder Auslesen von Informationen.x. jRegistryKey. in dem Sie Ihre JavaAnwendung aufrufen werden. oder in das Verzeichnis.4.prefs.node("/carpelibrum").put("Name".B.dll: Extrahieren Sie diese Datei und kopieren Sie sie in ein Verzeichnis.y. userPrefs. die von einem Java-Programm stammen.length.202 >> Auf die Windows-Registry zugreifen kann ein Java-Programm beliebige eigene Knoten durch Aufruf der Methode node() generieren und diesen dann Schlüssel mit Werten zuweisen bzw. Es ist nicht möglich. lesen.printStackTrace().true).jar: Extrahieren Sie diese Datei und nehmen Sie sie in den CLASSPATH Ihrer Java-Anwendung auf. die bereits eine fertige JNI-Lösung bereitstellt. // Alle Schlüssel wieder auslesen String[] keys = userPrefs.keys(). Laden Sie hierzu von http:// sourceforge.userRoot(). import java. userPrefs.out.putInt("Anzahl". for(int i = 0. } } } Listing 86: Zugriff auf Java-spezifische Registry-Einträge Der Einsatz des Pakets java. Für spezielle Datentypen wie boolean oder int stehen auch besondere putXXX()-Methoden bereit. Aufruf der Windows Registry API Den vollen Zugriff auf die Registry erhält man nur durch Einsatz der Windows API.putBoolean("online".z. "Peter"). // mehrere Schlüssel mit Werten erzeugen userPrefs. } catch(Exception e) { e.zip (aktuelle Version 1.util.util. . 5). was bei einem Java-Programm per JNI erfolgen kann. Das ZIP-Archiv enthält zwei wichtige Dateien: jRegistryKey. Glücklicherweise existiert eine sehr brauchbare OpenSource-Bibliothek namens jRegistryKey.

RegistryKey sub = key.*. ValueType.REG_SZ (null-terminated String = endet mit dem Zeichen '\0').a.>> System 203 Die Bibliothek stellt zwei zentrale Klassen bereit: RegistryKey repräsentiert einen Schlüssel und RegistryValue steht für einen Schlüsselwert. "ProgData") * @return RegistryKey-Objekt oder null bei Fehler */ public RegistryKey createSubKey(RootKey root.String name) { try { RegistryKey key = new RegistryKey(root. schon vorhanden) */ public RegistryKey createKey(RootKey root.*.String parent. u. return key. name).B.win32. parent). "Software\\Carpelibrum\\ProgData") * @return RegistryKey-Objekt ode null bei Fehler * (z.createSubkey(name). class WindowsRegistry { /** * Erzeugt einen neuen Schlüssel * * @param root Schlüssel-Wurzel. key.util."Software\\Carpelibrum") * Vaterschlüssel muss existieren * @param name Name des Unterschlüssels (z.beq. return null.REG_DWORD (32 Bit Integer).java System . String name) { try { RegistryKey key = new RegistryKey(root. Lesen von Registry-Werten erfolgt mit Hilfe der RegistryKey-Methoden setValue() bzw. die als Konstanten definiert sind. Das folgende Beispiel demonstriert den Einsatz dieser Klassen: /** * Klasse für den Zugriff auf die Windows-Registry via JNI */ import ca. ValueType. RootKey. RootKey.create().B.util. } } /** * Erzeugt einen neuen Unterschlüssel * @param root Schlüssel-Wurzel.B.REG_BINARY (Binärdaten) und ValueType. } catch(Exception e) { e. getValue().B. import java. Das Setzen bzw.HKEY_CURRENT_USER * @param parent voller Pfad des Vaterschlüssels * (z.HKEY_CURRENT_USER * @param name voller Pfad des Schlüssels * (z.B.registry. z. Hierbei gibt es verschiedene Datentypen. z.B. Listing 87: WindowsRegistry.printStackTrace().

Listing 87: WindowsRegistry. return null. } catch(Exception e){ e. return false.printStackTrace(). } System } /** * Löscht einen Schlüssel */ public boolean deleteKey(RootKey root.204 >> Auf die Windows-Registry zugreifen return sub. name). String name) { RegistryKey result = null. } catch(Exception e) { e. } } /** * Liefert den gewünschten Schlüssel */ public RegistryKey getKey(RootKey root.java (Forts.printStackTrace(). } catch(Exception e) { e. try { RegistryKey key = new RegistryKey(root. return true. try { result = new RegistryKey(root. if(result. } /** * Liefert alle Unterschlüssel zu einem Schlüssel */ public ArrayList<RegistryKey> getSubkeys(RootKey root. String name) { ArrayList<RegistryKey> result = new ArrayList<RegistryKey>(). name).delete().exists() == false) result = null.printStackTrace().) . String name) { try { RegistryKey key = new RegistryKey(root. } return result. name). key.

"Software"). while(it.hasNext()) { RegistryKey rk = (RegistryKey) it. import java.*. // Alle Unterschlüssel von HKEY_CURRENT_USER\Software ausgeben ArrayList<RegistryKey> subs = registry.createKey(RootKey.java (Forts. for(RegistryKey k : subs) System. Listing 88: Zugriff auf Windows-Registry via JNI System .println("\nInhalt von HKEY_CURRENT_USER\\Software :"). } } } catch(Exception e) { e. if(key == null) key = registry.getKey(RootKey. // Schlüssel HKEY_CURRENT_USER\Software\Carpelibrum anlegen falls // noch nicht vorhanden RegistryKey key = registry.printStackTrace().hasSubkeys()) { Iterator it = key.win32.HKEY_CURRENT_USER. Es liest alle Unterschlüssel von HKEY_CURRENT_USER\Software aus und trägt einen eigenen Schlüssel ein. "Software\\Carpelibrum").getSubkeys(RootKey. result.registry.HKEY_CURRENT_USER.getName()). import ca. } return result.>> System 205 if(key.out.) Das Start-Programm zu diesem Rezept demonstriert den Zugriff auf die Windows Registry. "Software\\Carpelibrum").add(rk).subkeys(). } } Listing 87: WindowsRegistry.*.util.util.println(k. public class Start { public static void main(String[] args) { try { WindowsRegistry registry = new WindowsRegistry().beq. System.next().HKEY_CURRENT_USER.out.

der initialisiert und laufbereit ist.out.out. Dies ist oft unschön.getStringValue()). System.) Abbildung 42: Auflistung von Registry-Einträgen 82 Abbruch der Virtual Machine erkennen Da ein Java-Programm immer innerhalb einer Virtual Machine (VM) läuft. key. = new RegistryValue("Anzahl".hasNext()) { RegistryValue value = (RegistryValue) it.println(value.3 gibt es glücklicherweise einen so genannten ShutdownHook-Mechanismus. "Mustermann"). 5). da hierdurch einem Programm keine Gelegenheit mehr bleibt. greift der . der teilweise Abhilfe schafft.getName() + " : " + value.getName()).println("Werte von " + key.next(). Erst wenn das Programm beendet wird. } } } Listing 88: Zugriff auf Windows-Registry via JNI (Forts. } } } catch(Exception e) { e.values(). // Werte wieder auslesen if(key. aber während der normalen Programmausführung nicht aktiv ist. Ein ShutdownHook ist ein Thread.206 >> Abbruch der Virtual Machine erkennen // Werte setzen RegistryValue name RegistryValue anzahl key.printStackTrace(). führt das Beenden der VM auch zum Abwürgen des Programms. while(it. interessante Daten wie bisherige Resultate oder LogInformationen auf die Festplatte zu sichern.setValue(name). System = new RegistryValue("Name". Seit Java 1. Iterator it = key.hasValues()) { System.setValue(anzahl).

getValue()). Listing 90: Abbruch der Virtual Machine erkennen System . public ShutdownHook(Start t) { parent = t. Unter Unix/ Linux werden alle Beendigungen erkannt. kann man jederzeit mit Runtime. aber nicht bei einem regulären Programmende.>> System 207 Hook und wird als Letztes ausgeführt.removeShutdownHook(Thread t) den Hook wieder abmelden.println("Programm wurde abgebrochen").out. Das war es schon. die man mit Hilfe der Methode Runtime. /** * Modellklasse für ShutdownHook zum Abfangen von STRG-C */ class ShutdownHook extends Thread { private Start parent. und zwar sowohl bei einem regulären Programmende als auch einem vorzeitig erzwungenen Ende durch Beenden der VM über die Tastenkombination (Strg)+(C).getRuntime(). Der Einsatz des Hook-Mechanismus ist sehr einfach. Falls man nur für bestimmte Programmphasen einen Hook als Rückversicherung haben möchte. Dies ist beispielsweise praktisch.java Das Start-Programm zu diesem Rezept addiert in einer for-Schleife Integer-Zahlen. /** * Aufrufbeispiel: STRG-C führt zur Ausgabe des letzten Wertes */ public class Start { private int value = 0. Im Falle eines vorzeitigen Abbruchs mit (Strg)+(C) wird der ShutdownHook ausgeführt.addShutdownHook(Thread t) registriert. der den letzten Zwischenwert ausgibt.println("Letzte Zwischensumme: " + parent. In diesem Fall sollte die letzte Anweisung der Aufruf von removeShutdownHook() sein. public void doWork() { ShutdownHook hook = new ShutdownHook(this). die das Signal SIGINT auslösen. falls der Hook nur bei einem vorzeitigen Abbruch abgearbeitet werden soll. } public void run() { System. } } Listing 89: ShutdownHook.out. System. H in we is Das Beenden per Taskmanager unter Windows wird leider nicht erkannt. Runtime rt = Runtime. Man definiert eine eigene Thread-Klasse.

bevor das Programm zwangsweise beendet wird.removeShutdownHook(hook). rt. wenn innerhalb eines Konsolenfensters die Tastenkombination (Strg)+(C) gedrückt wird (woraufhin die Java Virtual Machine und damit auch das ausgeführte Java-Programm abgebrochen werden). } catch(Exception e) { } } System. s.doWork(). } public int getValue() { return value. Eines der wichtigsten Signale ist SIGINT. .sleep(500). for(int i = 0 . das einen Programmabbruch signalisiert.) System Abbildung 43: Abbruch der VM erkennen 83 Betriebssystem-Signale abfangen Ein Betriebssystem kann jedem laufenden Programm Signale senden. Rezept 82 zeigte. } } Listing 90: Abbruch der Virtual Machine erkennen (Forts.addShutdownHook(hook). wenn das Programm einfach weiterarbeiten und das Betriebssystem-Signal ignorieren soll? Dann hilft auch kein ShutdownHook. Was aber. try { Thread.208 >> Betriebssystem-Signale abfangen rt. i++) { value = value + i. wie Sie mit Hilfe eines so genannten Shutdown-Hooks noch Aufräumarbeiten oder Speicheraktionen etc. i < 50.println("Endsumme: " + value). } public static void main(String[] args) { Start s = new Start(). wie er auf Windows-Rechnern beispielsweise ausgelöst wird. durchführen.out.

println(counter++). Eine Instanz der Klasse SignalHandler mit einer Implementierung der Methode handle().misc. der Sie den Namen des zu fangenden Signals übergeben. System. dass sie in zukünftigen Java-Versionen weiterhin vorhanden sein werden.misc. Der Name ist dabei der Betriebssystem-typische Signalname ohne »SIG«. INT für SIGINT.sleep(2000).handle(new Signal("INT").SignalHandler. die zwei Parameter erwartet: Eine Instanz der Klasse Signal. int counter = 0.B.SignalHandler. Das folgende Beispiel demonstriert das Abfangen des Signals SIGINT: System import sun. sie sind offiziell nicht vorhanden.err. und es gibt daher keine Garantie dafür. } catch(Exception e) { } } } } Listing 91: Signale abfangen Zur Einrichtung einer Signalbehandlung rufen Sie die statische Methode Signal.Signal.flush(). while(counter <= 10) { try { Thread.. import sun.>> System 209 Als Lösung bietet Sun zwei inoffizielle Klassen an: sun. System. new SignalHandler () { public void handle(Signal sig) { System. Mache weiter.err.out. public class Start { public static void main(String[] args) { // Signal SIGINT ignorieren Signal. } }). Eine entsprechende Warnung wird beim Kompilieren ausgegeben.Signal sowie sun. .misc.handle() auf."). also z.. Diese Klassen erscheinen in keiner Dokumentation.println("SIGINT wird ignoriert.misc.

210 >> Betriebssystem-Signale abfangen System Abbildung 44: Programm ignoriert Signal SIGINT .

auf t folgt ein weiteres Zeichen: H (Stunde).Nachkomma]Typ Angaben in [] sind dabei optional1. M (Minute). zur Verfügung stehen: Typ c d x f s t Beschreibung Darstellung als Unicode-Zeichen Dezimal: Integer zur Basis 10 Hexadezimal: Integer zur Basis 16 Gleitkommazahl String Zeit/Datum.Ein. Breite gibt die Anzahl an auszugebenden Zeichen an.out.3f nennt man PI. S (Sekunde).B. 1. eine Beschränkung auf beispielsweise zwei Nachkomma-Stellen ist nicht möglich.und drei Nachkommastellen aus: double pi = 3. System. System. So gibt der folgende Code den Wert von pi mit einer Vorkomma. + (Vorzeichen immer ausgeben). der man als ersten Parameter den eigentlichen Ausgabetext – ergänzt um spezielle Formatplatzhalter % für die auszugebenden Variablenwerte – und anschließend die Variablen übergibt.". m (Monat).printf("Die Zahl %1. z.println("Wert von x:" + x).141592654. so dass die einfachste Formatanweisung %Typ lautet.141592654 wird exakt so ausgegeben. Die [] selbst werden nicht angegeben! Ein. Der Typ definiert die Art der Daten. Y (Jahr).out. (Eine double-Zahl wie 3.und Ausgabe (IO) 84 Auf die Konsole (Standardausgabe) schreiben Die normale Konsolenausgabe erfolgt über den Ausgabestream System. 0 (Auffüllen der Breite mit Nullen).out und die allseits bekannten Methoden print() bzw.out. D (Datum als TagMonat-Jahr) Darstellung des Prozentzeichens % Tabelle 27: Typspezifizierer für printf() Die wichtigsten Werte für Flags sind: ^ (Umwandlung in Großbuchstaben). println().und Ausgabe . Die Syntax für eine Formatangabe ist: %[Index$][Flags][Breite][. d (Tag). System.) Glücklicherweise bietet Java mittlerweile die Methode printf() an. was insbesondere bei der Ausgabe von Gleitkommazahlen sehr unschön ist. pi).println("Hallo Leute!"). // x sei eine Variable Formatierte Ausgabe Der große Nachteil dieser Methoden ist die mangelnde Formatierfähigkeit.

float f= 3. Um die Umlaute dennoch korrekt auszugeben.out. Das traurige Ergebnis: Die deutschen Umlaute sowie etliche weitere Umlaute und Sonderzeichen. Date dt = new java. dt).und Ausgabe System. %02d %s %2. Ein.out.Date. System. müssen Sie entweder auf das in Java 6 neu eingeführte Console-Objekt zurückgreifen oder die Zeichenkodierung explizit vorgeben.printf("Nr. die die Konsole prinzipiell anzeigen könnte.util.util.Date().out auf die Windows-Konsole ausgeben. ohne dass dabei allerdings eine korrekte Umkodierung in den 8-Bit-OEM-Zeichensatz der Konsole stattfinden würde. public class Start { public static void main(String[] args) { int index = 4.1f %% Zeit %4$tH:%4$tM:%4$tS \n". index. . werden die 16-BitCodes der einzelnen Zeichen auf je 8-Bit zurechtgestutzt. String txt = "Wahlanteil". txt.212 >> Umlaute auf die Konsole (Standardausgabe) schreiben import java. f. } } Listing 92: Datenausgabe mit printf() Abbildung 45: Ausgabe des Start-Programms 85 Umlaute auf die Konsole (Standardausgabe) schreiben Wenn Sie Java-Strings via System.println(). gehen verloren.75f.

} } } Listing 93: Ausgabe von Umlauten auf die Konsole Ein.console().*. ß \n").io. die wie die gleichnamige Methode von System. ß \n").Console. import java. Wenn Ihr Java-Code im Kontext einer Java Virtual Machine-Instanz ausgeführt wird. ö.console(). ü. können Sie diese mit beliebigen Ausgabestreams verbinden und auch die Zeichenkodierung frei (soweit verfügbar) wählen. public class Start { public static void main(String[] args) { // Zugriff auf das Console-Objekt Console cons = System. cons.printf(" ä.console()-Aufruf an: System. cons. die mit einem Konsolenfenster verbunden ist. Umlaute über PrintStream ausgeben Hinter System. Hängen Sie in solchen Fällen den printf()-Aufruf einfach an den System.printf(" ä. System.printf(" Ausgabe der Umlaute mit Console \n").>> Ein.printf("\n"). T ip p . ü. Wenn Sie eigene PrintStreamObjekte erzeugen.console(). welches das Konsolenfenster repräsentiert.io.und Ausgabe (IO) 213 Umlaute über Console ausgeben Zur formatierten Ausgabe von Strings definiert die Klasse Console eine Methode printf(). ö. Die Klasse Console instanzieren Sie nicht selbst.out arbeitet (siehe Rezept 84) – nur eben mit dem Unterschied. den Verweis auf das Console-Objekt in einer eigenen Variablen zu speichern.und Ausgabe Für vereinzelte Ausgaben lohnt es sich nicht. Über die statische Console-Methode console() können Sie sich eine Referenz auf dieses Objekt zurückliefern lassen. dass die Zeichen in den OEM-Zeichensatz der Konsole umkodiert werden. das mit der Konsole als Ausgabegerät verbunden ist und die Standard-Zeichenkodierung verwendet. // Ausgabe if (cons != null) { cons.printf("\n Ausgabe der Umlaute mit Console \n").out verbirgt sich ein PrintStream-Objekt. import java. public class Start { public static void main(String[] args) { PrintStream out. erzeugt die JVM automatisch intern ein Console-Objekt.

das das Ziel der Ausgabe vorgibt (hier die Konsole) true.printf(" ä. name. System. und Sie müssen die Methode flush() aufrufen) den Namen der gewünschten Zeichenkodierung (hier "Cp850" für die DOS-Codepage 850) H inwe is Vor Java 6 war dies der übliche Weg.out.in. } } Ein.parseInt( in. ß \n"). } catch (UnsupportedEncodingException e) { out = System.printf(" %s (Alter: %d) \n".214 >> Von der Konsole (Standardeingabe) lesen try { out = new PrintStream(System. 86 Von der Konsole (Standardeingabe) lesen Konsolenanwendungen bedienen sich zum Einlesen von Daten über die Tastatur traditionell des Eingabestreams System. ö.und Ausgabe Dem PrintStream-Konstruktor werden drei Argumente übergeben: ein OutputStream-Objekt.out. age = Integer. try { System. damit die Ausgaben sofort ausgeführt werden (andernfalls werden die Ausgaben gepuffert.print(" Geben Sie Ihr Alter ein: "). "Cp850").print(" Geben Sie Ihren Namen ein: ").out. true. out. System.printf("\n"). } out.readLine() ).printf(" Ausgabe der Umlaute mit PrintStream \n"). .readLine().in)). age).out.out. } catch (IOException e) {} Hi nwei s Für ausführlichere Informationen zur Umwandlung von Stringeingaben in Zahlen siehe Rezept 87. out. um Umlaute auf die Konsole auszugeben. um den dann meist ein BufferedReader-Objekt aufgebaut wird. ü. name = in. BufferedReader in = new BufferedReader(new InputStreamReader(System.

.print(" Geben Sie Ihren Namen ein: "). name. H in we is H in we is Umlaute.parseInt( cons. System. name = cons.printf(" %s (Alter: %d) \n". wenn Sie die eingelesenen Strings in eine Datei schreiben oder in eine grafische Benutzeroberfläche einbauen. cons. Schließlich können Sie den Text zur Eingabeaufforderung direkt an readLine() übergeben: Console cons = System.out. age). .out. werden bei Ausgabe auf die Konsole mit System. Probleme gibt es allerdings. Einlesen mit Scanner Zum Einlesen und Parsen können Sie sich auch der Klasse Scanner bedienen: Scanner sc = new Scanner(System. Probleme gibt es allerdings. Einlesen mit Console Seit Java 6 können Sie auch das vordefinierte Console-Objekt zum Einlesen verwenden.nextLine(). werden bei Ausgabe auf die Konsole mit System. die über die Tastatur eingelesen wurden.und Ausgabe cons.>> Ein. age = sc.printf (" %s (Alter: %d) \n".nextLine().in). Ein. age = Integer.console().. die Umlaute enthalten. name = cons. sc.und Ausgabe (IO) 215 Umlaute. wenn Sie die eingelesenen Strings in eine Datei schreiben oder in eine grafische Benutzeroberfläche einbauen. Weniger offensichtlich. System. Dann sollten Sie entweder auf Console umsteigen (siehe unten) oder den InputStreamReader mit passender Zeichenkodierung erzeugen.out korrekt angezeigt.out. dass eingelesene Strings. cons.readLine() ).printf(" Geben Sie Ihren Namen ein: "). wodurch der Code übersichtlicher wird. Dann sollten Sie das Scanner-Objekt auf der Basis des internen Readers des Console-Objekts erzeugen: Scanner sc = new Scanner(System.readLine(" Geben Sie Ihren Namen ein: "). Console cons = System.print(" Geben Sie Ihr Alter ein: "). Die auffälligste Veränderung gegenüber der BufferedReader-Konstruktion ist das Wegfallen der geschachtelten Konstruktoraufrufe und der Exception-Behandlung. aber möglicherweise noch interessanter ist.printf(" Geben Sie Ihr Alter ein: ").reader()).out korrekt angezeigt.. name. Die Ausgabe auf die Konsole muss dann ebenfalls über das Console-Objekt erfolgen.readLine().console(). die über die Tastatur eingelesen wurden. age). System. name = sc.nextInt(). problemlos in GUI-Oberflächen eingebaut oder über das Console-Objekt wieder auf die Konsole ausgegeben werden können.console().

equals(new String(passwort))) cons. Mit Hilfe passender Methoden der Klasse System können sie umgeleitet werden. cons. System.printf(" Passwort eingeben: ").printf(" Anmeldung fehlgeschlagen! \n").in auf den Eingabestream stream.err sind per Voreinstellung mit der Konsole verbunden. Setzt System.und Ausgabe Ausgabe: Benutzername eingeben: Dirk Passwort eingeben: Dirk. cons. name = cons. cons.216 >> Passwörter über die Konsole (Standardeingabe) lesen 87 Passwörter über die Konsole (Standardeingabe) lesen Das Einlesen von Passwörtern oder anderweitigen sensiblen Daten über die Konsole war in Java früher ein großes Problem.printf(" %s. name). weil jeder Umstehende die Eingaben mitlesen konnte. public class Start { private final static String PASSWORT = "Sesam". char passwort[]. Methode static setIn(InputStream stream) static setErr(PrintStream stream) static setOut(PrintStream stream) Beschreibung Setzt System. Sie sind angemeldet! 88 Standardein. Setzt System. Doch diese Einstellung ist nicht unabänderlich.console(). import java.und -ausgabe umleiten Die Standardstreams System.printf("\n"). Sie sind angemeldet! \n".*. if (PASSWORT. } } Listing 94: Geheime Daten in Konsolenanwendungen einlesen Ein.io. passwort = cons. public static void main(String[] args) { String name. Console cons = System.printf(" Benutzername eingeben: "). Dank der Console-Methode readPassword() gehören diese Probleme der Vergangenheit an.readPassword().err auf den Ausgabestream stream.readLine().in und System. else cons. Tabelle 28: Methoden in System für die Umleitung der Standardein-/-ausgabe .out.out auf den Ausgabestream stream.

*. ta.*. Die Klasse TextAreaPrintStream löst dieses Problem. die die an die Standardausgabe geschickten Zeichencodes in die JTextArea schreibt.valueOf(c)). um die Standardausgabe in eine JTextArea umzuleiten. } } // Hilfsklasse. import java.io. public TextAreaOutputStream(JTextArea ta) { this.swing.append(String.und Ausgabe Ein kleines Problem ist. Die Referenz auf die JTextArea übernimmt die Klasse als Konstruktorargument.java – PrintStream-Klasse zur Umleitung der Standardausgabe in eine JTextArea Ein. erweitert der Java-Compiler den Konstruktorcode automatisch um den Aufruf eines Standardkonstruktors (Konstruktor ohne Parameter) der Basisklasse. Zur Erinnerung: Der Konstruktor einer abgeleiteten Klasse ruft als erste Anweisung immer einen Konstruktor der Basisklasse auf.>> Ein. die OutputStream für JTextArea erzeugt class TextAreaOutputStream extends OutputStream { private JTextArea ta.ta = ta. /* * Klasse zur Umleitung der Standardausgabe in eine JTextArea */ public class TextAreaPrintStream extends PrintStream { public TextAreaPrintStream(JTextArea ta) { super(new TextAreaOutputStream(ta)). indem sie den Basisklassenkonstruktor mit dem OutputStream-Argument aufruft – allerdings mit einer abgeleiteten OutputStream-Klasse.und Ausgabe (IO) 217 Die nachfolgend definierte Klasse TextAreaPrintStream ist beispielsweise geeignet. die ein File-Objekt. import javax. einen Dateinamen oder einen OutputStream als Argument erwarten. Ist ein entsprechender super-Aufruf im Quelltext des Konstruktors nicht vorgesehen. } public void write(int b) { char c = (char) b. Die Klasse muss zu diesem Zweck von PrintStream abgeleitet werden – passend zum Argument der setOut()-Methode. dass die Basisklasse PrintStream nur Konstruktoren definiert. H in we i s . In dieser OutputStream-Klasse wird dann die write()-Methode überschrieben. } } Listing 95: TextAreaPrintStream. deren Konstruktor die Referenz auf die JTextArea-Instanz übergeben werden kann.

JButton btn2 = new JButton("B ausgeben"). java. java. } }). BorderLayout.*. 24)). p.setFont(new Font("Dialog". // Panel mit zwei Schaltern JPanel p = new JPanel(). p.*. 24)). java.setFont(new Font("Dialog".println(" B").218 >> Standardein.awt.add(logpane.CENTER).getViewport(). JTextArea logpane = new JTextArea(). Font.add(btn2). in die die Ausgaben umgeleitet werden. p. btn1.java – Umlenken der Standardausgabe in eine JTextArea . die beim Drücken einen Text an die Standardausgabe schicken. dessen ContentPane mittels einer JSplitPane-Instanz in zwei Bereiche unterteilt ist: einem Arbeitsbereich mit zwei JButton-Instanzen.awt.PLAIN. // JTextArea zum Protokollieren der Schalterklicks JScrollPane scrollpane = new JScrollPane().addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.event. scrollpane).addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System. btn2.out.PLAIN. scrollpane. null).add(btn1). einen Logging-Bereich mit der JTextArea-Komponente.out. getContentPane(). public class Start extends JFrame { public Start() { // Hauptfenster konfigurieren setTitle("Umlenken der Standardausgabe").add(splitpane.io.und Ausgabe javax. // Schalter-Panel und JTextArea in SplitPane einfügen JSplitPane splitpane = new JSplitPane(JSplitPane. import import import import Ein. btn2. Font.swing.VERTICAL_SPLIT. Listing 96: Start.und -ausgabe umleiten Das Programm zu diesem Rezept ist ein GUI-Programm.*. btn1.*. } }).println(" A"). JButton btn1 = new JButton("A ausgeben").

>> Ein.setVisible(true). frame. das Programm – in der falschen Annahme. 90 Fortschrittsanzeige für Konsolenanwendungen Auch Konsolenanwendungen führen hin und wieder länger andauernde Berechnungen durch. frame. ohne dass irgendwelche Ergebnisse auf der Konsole angezeigt werden.) Abbildung 46: JTextArea mit umgelenkten println()-Ausgaben 89 Konsolenanwendungen vorzeitig abbrechen Konsolenanwendungen.300). Ungeduldige Anwender kann dies dazu verleiten. die sich aufgehängt haben oder deren Ende Sie nicht mehr abwarten möchten.java – Umlenken der Standardausgabe in eine JTextArea (Forts.setSize(500.EXIT_ON_CLOSE).und Ausgabe public static void main(String args[]) { Start frame = new Start(). frame.setOut(out). es sei bereits abgestürzt – abzubrechen. } . } } Listing 96: Start. sollten Sie das Programm in regelmäßigen Abständen Lebenszeichen ausgeben lassen. können auf den meisten Betriebssystemen durch Drücken der Tastenkombination (Strg) +(C) abgebrochen werden. System.setLocation(300. setDefaultCloseOperation(JFrame.300).und Ausgabe (IO) 219 // Standardausgabe auf JTextArea umlenken TextAreaPrintStream out = new TextAreaPrintStream(logpane). Ein. Um dem vorzubeugen.

Entscheidend ist. eine Fortschrittsanzeige zu implementieren."). Ein.out.out. Lebenszeichen. müssen Sie überlegen.out. scheiden in 99% der Fälle ganz aus. ++i) { // tue so. beispielsweise eines Punkts. } System. Listing 97: Aus Start.java – Lebenszeichen mit Schleife . // 3. dass die Konsole nicht unnötig weit nach unten gescrollt wird.print(" Bitte warten"). dass nun mit einer längeren Wartezeit zu rechnen ist."). Lebenszeichen periodisch ausgeben System. ohne Leerzeichen oder Zeilenumbrüche: System. // 2. als würde intensiv gerechnet Thread.\n"). for(int i = 0. (Droht die Konsole mit Lebenszeichen überschwemmt zu werden. Gleiches gilt für die Ausgabe von akustischen Signalen. bietet es sich an. Bei Ausgabe von Lebenszeichen auf die Konsolen sollten Sie vor allem darauf achten.print(".println("\n Berechnung beendet. die vom Anwender bestätigt werden müssen. i < 12.220 >> Fortschrittsanzeige für Konsolenanwendungen Es gibt unzählige Wege. (Gegen die Verbindung des Endes der Berechnung mit einem akustischen Signal ist jedoch nichts einzuwenden.sleep(400). deren Fortschreiten Sie durch Lebenszeichen verdeutlichen wollen. Die Anzahl der Lebenszeichen sollte größer als 4 sein. aber nicht zu groß werden.out. aber auch nicht zu lange auf sich warten lassen.) Informieren Sie den Anwender vorab. eine passende Form und einen geeigneten Zeitabstand zwischen den einzelnen Lebenszeichen (respektive Aktualisierungen der Fortschrittsanzeige) zu finden: Die Lebenszeichen sollten den Anwender unaufdringlich informieren.out. wie Sie für eine periodische Ausgabe der Lebenszeichen sorgen. so setzen Sie lieber den Zeitabstand zwischen den Lebenszeichen herauf. die Lebenszeichen innerhalb dieser Schleife auszugeben: // 1. Nachdem Sie Form und Frequenz der Lebenszeichen ungefähr festgelegt haben. Gut geeignet ist die periodische Ausgabe eines einzelnen Zeichens. Zeitaufwendige Berechnung ankündigen System. eine große äußere Schleife durchläuft.print(". Fortschrittsanzeigen mit Schleifen Wenn die Berechnung.und Ausgabe Die Lebenszeichen sollten nicht zu schnell aufeinander folgen.) Andere Ausgaben sollten durch die Fortschrittsanzeige möglichst wenig gestört werden. Ergebnis anzeigen System.println().

print(" Bitte warten").print(". müssen Sie mehrere Lebenszeichen über die Schleife verteilen (eventuell gibt es innere Schleifen. die sich besser zur Ausgabe der Lebenszeichen eignen). setzen Sie die Anzahl der Lebenszeichen herab.java – Lebenszeichen mit Timer Ein. in deren run()-Methode Sie ein Lebenszeichen ausgeben lassen. Listing 99: Aus Start. // Zeitgeber für Fortschrittsanzeige starten und alle 400 ms // ausführen lassen.und Ausgabe Fortschrittsanzeigen mit Timern Durch Timer gesteuerte Fortschrittsanzeigen sind aufwändiger zu implementieren. haben aber den Vorzug.out. während im Hintergrund der Thread des Timers ausgeführt und in den festgelegten periodischen Abständen die run()-Methode des TimerTask-Objekts ausgeführt wird. dritten .out.. Wird die Schleife zu selten durchlaufen. /** * TimerTask-Klasse für Konsolen-Fortschrittsanzeige */ class ShowProgressTimer extends TimerTask { public void run() { System. eine anfängliche Verzögerung und die Dauer des Zeitintervalls (in Millisekunden). Schleifendurchgang Lebenszeichen ausgeben lassen: if( i%2) System."). indem Sie nur bei jedem zweiten.").print(". Anschließend folgt die eigentliche Berechnung. ..und Ausgabe (IO) 221 Bei dieser Form entspricht die Anzahl der Lebenszeichen den Durchläufen der Schleife.TimerTask. dass die Lebenszeichen in exakt festgelegten Zeitintervallen ausgegeben werden können. Wird die Schleife sehr oft durchlaufen. Zuerst definieren Sie eine TimerTask-Klasse.>> Ein. Timer timer = new Timer(). beispielsweise: import java. Nach Abschluss der Berechnung beenden Sie den Timer.out. Vor dem Start der Berechnung geben Sie eine Vorankündigung aus. } } Listing 98: Einfache TimerTask-Klasse für Konsolen-Fortschrittsanzeigen Danach wechseln Sie zum Code der Berechnung.util. int aMethod() throws InterruptedException { // Zeitaufwändige Berechnung ankündigen System. erzeugen ein Timer-Objekt und übergeben diesem eine Instanz der TimerTask-Klasse.

den Code für einen Befehl einzugeben. Zu jedem Befehl muss ein Code angegeben werden.sleep(12*1000). // Ergebnis zurückliefern return 42.und Ausgabe Listing 99: Aus Start. 2. Anzeigen des Menüs Die Ausgabe erfolgt zeilenweise mit println()-Aufrufen..console().222 >> Konsolenmenüs timer. soweit sie nicht in Schritt 3 vom default-Block der switch-Anweisung abgefangen werden (beispielsweise Strings aus mehr als einem Zeichen).out.printf(" System.console(). // Zeitgeber für Fortschrittsanzeige beenden timer. Ungültige Eingaben. da diese in Schritt 3 mittels einer switch-Verzeigung ausgewertet werden können.java – Lebenszeichen mit Timer (Forts.) Abbildung 47: Fortschrittsanzeigen in Konsolenanwendungen 91 Konsolenmenüs Bei Menüs denken die meisten Anwender an Menüs von GUI-Anwendungen. . 0. Die Implementierung eines Konsolenmenüs besteht aus drei Schritten: 1. 400). Erster Menübefehl Zweiter Menübefehl <a> \n"). der eine menügesteuerte Programmführung opportun macht.println(). System. Doch auch Konsolenanwendungen können einen Leistungsumfang erreichen. } Ein. als würde intensiv gerechnet Thread. // tue so. Abfragen der Benutzerauswahl Der Anwender wird aufgefordert.cancel(). Integer-Werte oder Aufzählungskonstanten.printf(" . <b> \n"). Gut geeignet sind hierfür Zeichen. müssen aussortiert werden.schedule(new ShowProgressTimer(). über den der Anwender den Befehl auswählen kann. System..

reader()).console().>> Ein. Grundstruktur Soll das Menü nicht nur einmalig zu Beginn des Programms angezeigt werden.printf(" Menü \n").out.und Ausgabe (IO) 223 3. System.printf(" ******************************** \n").print("\n Ihre Eingabe : "). input = scan. Eingabe lesen option = NO_OPTION. String input. char option = NO_OPTION. in der Menue wiederholt angezeigt und Befehle abgearbeitet // werden.console().printf(" Dritter Menübefehl <c> \n"). Wird dieser Menübefehl ausgewählt. Scanner scan = new Scanner(System. // Schleife.console().length() == 1) // einzelnes Zeichen in Eingabe option = input.out.println("\n"). System. System.printf("\n"). import java.console().printf("\n"). System. if(input. Ein.Scanner. Listing 100: Konsolenanwendung mit Menü .printf(" Erster Menübefehl <a> \n").console(). dass Sie die obigen drei Schritte in eine do-while-Schleife fassen und das Menü um einen Menübefehl zum Verlassen des Programms erweitern.util. System. System. System.nextLine(). bis Befehl zum Beenden des Programms ausgewählt und die // Schleife verlassen wird do { // 1. System.console().console(). // 2.console().charAt(0).console(). Menu anzeigen System.und Ausgabe public class Start { public static void main(String[] args) { final char NO_OPTION = '_'.printf(" Programm beenden <q> \n"). wird die do-while-Schleife und damit das Programm beendet.printf(" Zweiter Menübefehl <b> \n"). System. gehen Sie so vor. Abarbeiten des Menübefehls Typischerweise in Form einer switch-Verzweigung.

console(). Ein. scan. Hat der Anwender aus Versehen mehrere Tasten gedrückt. default: System.printf(" } System. default: System.und Ausgabe // 4. Menübefehl abarbeiten switch(option) { case 'a': System..) Statt in der Schleifenbedingung zu prüfen.console().) Schritt 2 liest eine Eingabe von der Tastatur ein. switch(option) { . NO_OPTION wird daher vom default-Block der switch-Anweisung behandelt. Falsche Eingabe \n").console()..console().. } } T ip p . wird der Standardwert NO_OPTION weitergereicht.printf(" break.console(). Warten.printf(" break.printf(" Programm wird beendet \n"). das keinem Menübefehl entspricht. bis Anwender fortfahren will System.printf(" break.out. Menübefehl b \n"). } } Listing 100: Konsolenanwendung mit Menü (Forts. } while(option != 'q').console(). wird sie in option gespeichert und so an die switch-Anweisung weitergegeben.224 >> Konsolenmenüs // 3.console(). Programm wird beendet \n").println("\n").printf(" break.nextLine(). Besteht die Eingabe aus einem einzelnen Zeichen. (NO_ OPTION wurde zu Beginn der main()-Methode mit einem Zeichen initialisiert. break quit. case 'c': System.console(). Menübefehl a \n").printf(" Falsche Eingabe \n"). case 'q': System. Menübefehl c \n"). können Sie die Schleife auch mit einer Label-Sprunganweisung aus der switch-Verzweigung heraus verlassen: quit: while(true) { . case 'q': System. ob die Schleife weiter auszuführen ist (while(option != 'q')).. case 'b': System.printf(" <Enter> drücken zum Fortfahren \n").

Die Arbeit zur Implementierung eines Konsolenmenüs reduziert sich damit auf die Bearbeitung der Textdatei und das Aufsetzen der switch-Verzweigung zur Behandlung der verschiedenen Menübefehle. um Groß. break.und Kleinschreibung unterscheiden. Semikolon und Titel dürfen keine anderen Zeichen (auch kein Whitespace) stehen.printf(" Menübefehl b \n"). break.printf(" Programm wird beendet \n").console(). break.printf(" Falsche Eingabe \n"). Jede Zeile beginnt mit dem Zeichen.>> Ein. Die Textdatei mit den Menübefehlen besitzt folgendes Format: Jede Zeile repräsentiert einen Menübefehl.Zweiter Menübefehl c. Der Titel darf kein Semikolon enthalten.Programm beenden Listing 101: Beispiel für eine Menütextdatei . option = 'q'. das später zum Aufruf des Menübefehls einzugeben ist.und Ausgabe 92 Achtung Die Anweisung option = 'q' ist nötig. die in diesem Rezept vorgestellt wird.console().und Ausgabe (IO) 225 Groß.und Kleinschreibung unterstützen Wenn Sie im Buchstabencode zu den Menübefehlen nicht zwischen Groß.printf(" Menübefehl a \n"). a. damit die do-while(option != 'q') auch bei Eingabe von Q beendet wird.console(). System. kann die Anweisung entfallen. Die Titel der Menübefehle können jederzeit in der Textdatei geändert werden. können Sie Konsolenmenüs auf der Basis von Textdateien erstellen. System. System.console(). break. Ein. können Sie die switch-Verzweigung nutzen.printf(" Menübefehl c \n").console().und Kleinbuchstaben elegant auf die gleichen Menübefehle abzubilden: switch(option) { case 'A': case 'a': case 'B': case 'b': case 'C': case 'c': case 'Q': case 'q': default: } System.Erster Menübefehl b. System. Automatisch generierte Konsolenmenüs Mit der Klasse ConsoleMenu. Danach folgt ein Semikolon und anschließend der Titel des Menübefehls.Dritter Menübefehl q. ohne dass die Java-Quelldatei neu kompiliert werden muss (beispielsweise zur Lokalisierung des Programms). Wird die Schleife wie im vorangehenden Absatz beschrieben mit einer Sprunganweisung verlassen. Zwischen Codezeichen.

util.und Ausgabe Das Füllzeichen zwischen Befehlstitel und -code (hier der Punkt ....<c> Programm beenden..Scanner.code = code...<b> Dritter Menübefehl... java. Werden dabei Exceptions ausgelöst.io... Erkennt der Konstruktor Fehler im Dateiformat.....BufferedReader.. dem zu diesem Zweck der Name der Textdatei und das zu verwendende Füllzeichen übergeben werden. private String title. MenuElem(char code.. Während des Einlesens bestimmt der Konstruktor zusätzlich die Zeichenlänge des größten Titels (maxLength) sowie den numerischen Code des »größten« verwendeten Menübefehlszeichens. java.226 >> Automatisch generierte Konsolenmenüs Die Klasse ConsoleMenu erzeugt aus dieser Datei das folgende Menü: ******************************** Menue Erster Menübefehl. printPrompt() angepasst werden.) wird als Argument an den Konstruktor von ConsoleMenu übergeben.io. Die MenuElemObjekte wiederum werden in einer Vector-Collection verwaltet.<a> Zweiter Menübefehl. /** * Klasse zum Aufbau von Konsolenmenüs */ public class ConsoleMenu { private class MenuElem { private char code.<q> Ihre Eingabe : q Ein..Vector.title = title........... um die Konstante NO_OPTION sicher mit einem Zeichen initialisieren zu können. Im Anschluss an die while-Schleife füllt der Konstruktor alle Titel bis auf maxLength+10 Zeichen mit dem übergebenen Füllzeichen auf (damit die Menübefehlszeichen später rechtsbündig untereinander ausgegeben werden)... Der Konstruktor liest in einer while-Schleife die Zeilen der Textdatei. Der Konstruktor übernimmt alle nötigen Dateioperationen. Der numerische Code wird benötigt..io.... werden diese an den Aufrufer weitergegeben..IOException. java. extrahiert daraus die Informationen für die Menübefehle und speichert diese in einem Objekt der Hilfsklasse MenuElem. this.util.. java. import import import import import java...FileReader.. String title) { this. das mit keinem Menübefehl verbunden ist. löst er eine Exception der selbst definierten Klasse ParseMenuException aus. Die Menüüberschrift und die Eingabeaufforderung können in abgeleiteten Klassen durch Überschreibung der Methoden printHeader() bzw.. Das Einlesen des Menüs geschieht vollständig im Konstruktor.. } } // Hilfsklasse für Menüelemente .

// Menübefehle einlesen while( (line = in. ob Zeile korrekt aufgebaut ist if( line.length() < 3 || line.length().add(new MenuElem(code.>> Ein.clear().readLine()) != null) { // kurz prüfen.getNumericValue(code) > maxCode) ? Character. throw new ParseMenuException("Fehler beim Parsen der " + " Menuedatei").length.getNumericValue(code) : maxCode. ParseMenuException { // Textdatei mit Menü öffnen BufferedReader in = new BufferedReader(new FileReader(filename)).und Ausgabe . // Größte Titellänge festhalten maxLength = (title. for(MenuElem e : menu) { diff = (maxLength + 10) .'. menu.indexOf('. } // Alle Menütitel auf gleiche Länge plus 10 Füllzeichen bringen int diff. // Vektor mit // Menübefehlen 227 public ConsoleMenu(String filename. for(int i = 0. ++i) Ein. // Für jede Zeile Menübefehlinformationen auslesen. int maxCode = 0.e.' || line.substring(2).charAt(1) != '.charAt(0). title)). String title. title = line.close(). } code = line. in.length() > maxLength) ? title. 2) != -1) { menu.und Ausgabe (IO) public final char NO_OPTION. char paddChar) throws IOException. char[] pad = new char[diff]. // nicht belegtes Zeichen private Vector<MenuElem> menu = new Vector<MenuElem>(7). in MenuElem-Objekt // speichern und in Vector ablegen String line. // Größten Zeichencode festhalten maxCode = (Character.title.length() : maxLength. i < pad. char code. int maxLength = 0.

System. for(MenuElem e : menu) System. Die Eingabe des Anwenders können Sie selbst einlesen oder bequem mit getUserOption() abfragen. } public void printMenu() { printHeader().console(). e. char option = NO_OPTION. in. input = scan.console(). e.close().printf("\n").println().out.console().charAt(0). System.length() == 1) // einzelnes Zeichen in Eingabe option = input. } // Zeichen bestimmen.title.reader()).println("\n").228 >> Automatisch generierte Konsolenmenüs pad[i] = paddChar.printf("\n"). } public char getUserOption() { String input.nextLine(). e. System. System. } protected void printPrompt() { System.console().print(" Ihre Eingabe : ").printf(" ******************************** \n").printf(" %s<%s> \n". public class Start { public static void main(String args[]) { Listing 102: Verwendung der Klasse ConsoleMenu in einem Konsolenprogramm . if(input. System. return option.und Ausgabe Für die Ausgabe des Menüs müssen Sie lediglich die Methode printMenu() aufrufen.console().console().printf(" Menü \n").code). } protected void printHeader() { System. } } Ein.out.out.title += new String(pad). printPrompt(). das mit keinem Menübefehl verbunden ist NO_OPTION = (char) (100+maxCode). siehe Listing 102. Scanner scan = new Scanner(System.

console().printf(" <Enter> drücken zum Fortfahren \n").console().console(). System.'). } // Ende while } catch(Exception e) { System.und Ausgabe Menübefehl a \n"). quit: while(true) { menu. } } } Listing 102: Verwendung der Klasse ConsoleMenu in einem Konsolenprogramm (Forts.out.printf(" break quit.printf(" break.printf(" } System. try { menu = new ConsoleMenu("Menu. case 'B': case 'b': System.printMenu(). switch(option) { case 'A': case 'a': System. Menübefehl c \n"). case 'q': System. default: System.console().printf(" break.) Ein.err.out. case 'Q': option = 'q'. '. option = menu.console(). case 'C': case 'c': System.println("Fehler: " + e.>> Ein.println(). System.println("\n").getMessage()). .console(). Falsche Eingabe \n").printf(" break.nextLine(). ConsoleMenu menu. Menübefehl b \n").und Ausgabe (IO) 229 char option.getUserOption().txt". Programm wird beendet \n"). scan.

. Das Programm zu diesem Rezept erwartet auf der Kommandozeile drei Argumente: eine Zahl. Es prüft vorab.und Ausgabe Abbildung 48: Konsolenmenü 93 Konsolenausgaben in Datei umleiten Ausgaben. >| die erzwungene Umleitung. oder zu verarbeitende Daten. bash. Auf diese Weise können die Ausgaben auf bequeme Weise dauerhaft gespeichert werden. Die Ausgaben lassen sich mit anderen Programmen elektronisch weiterverarbeiten. java ProgrammName > Output. csh und tcsh unterstützen beispielsweise > die Umleitung durch Überschreiben. die das Verhalten des Programms steuern.txt Unter Linux bieten die meistens Shells gleich mehrere Symbole für die Umleitung von Konsolenausgaben in Dateien an. Sie können umfangreiche Ausgaben in einen Editor laden und mit dessen Suchfunktion durchgehen. was etliche Vorteile bringt: Sie können die Ausgaben mit den Ergebnissen späterer Programmsitzungen vergleichen. In der main()-Methode können Sie die im Array gespeicherten Kommandozeilenargumente abfragen und auswerten. die zur Konsole geschickt werden. 94 Kommandozeilenargumente auswerten Konsolenanwendungen besitzen die erfreuliche Eigenschaft.230 >> Konsolenausgaben in Datei umleiten Ein. ob der Anwender beim Aufruf die korrekte Anzahl Argumente übergeben hat. lassen sich auf den meisten Betriebssystemen durch Piping in Dateien umleiten. Unter Windows leiten Sie die Ausgaben mit > in eine Textdatei um. Der Java-Interpreter übergibt diese Argumente beim Programmstart an den args-Array-Parameter der main()-Methode. >> die Umleitung durch Anhängen. dass man ihnen beim Aufruf Argumente mitgeben kann – beispielsweise Optionen. ein Operatorsymbol und eine zweite Zahl.

println("Falsche Anzahl Argumente in Kommandozeile").out. // Befehl bearbeiten switch(operator) { case '+': System.und Ausgabe (IO) 231 Bei einer abweichenden Anzahl weist das Programm den Anwender auf die korrekte Aufrufsyntax hin und beendet sich selbst.println(" = " + (zahl1 * zahl2)). System.print("\n " + zahl1 + " " + operator + " " + zahl2).println(" = " + (zahl1 / zahl2)). public class Start { public static void main(String args[]) { System. Stimmt die Anzahl der Argumente.println("Aufruf: java Start " + "Zahl Operator Zahl <Return>\n").out.out. System. ob korrekte Anzahl Kommandozeilenargumente vorhanden if (args.println(" Ungueltiges Argument").out. break.out.charAt(0).java – Verarbeitung von Kommandozeilenargumenten Ein. die gewünschte Operation.println(). System.parseDouble(args[0]).println(" = " + (zahl1 . break. } } catch (NumberFormatException e) { System. // Prüfen.parseDouble(args[2]). break. default: System. } try { // Die Kommandozeilenargumente umwandeln double zahl1 = Double.out. double zahl2 = Double. break. } } } Listing 103: Start.err.println("Operator nicht bekannt").out.println(" = " + (zahl1 + zahl2)). case ':': case '/': System. break.>> Ein. case '-': System.zahl2)).out. case 'X': case 'x': case '*': System.out. sofern die Typumwandlung nicht zur Auslösung einer NumberFormatException geführt hat.exit(0). char operator = args[1].length != 3) { System.und Ausgabe . wandelt das Programm die Argumente in die passenden Datentypen um (Kommandozeilenargumente sind immer Strings) und berechnet.

und Ausgabe 95 H i n we i s Auf der Windows-Konsole führen Aufrufe mit * möglicherweise dazu.. der Verzeichnisse enthält. // liefert false! . In solchen Fällen kann man auf die wenig bekannte Methode mkdirs() zurückgreifen. Setzen Sie * dann in Anführungszeichen: »*«. falls sie noch nicht vorhanden sind: boolean status = f.txt"). boolean status = f. die alle Verzeichnisse auf dem angegebenen Pfad erzeugt.232 >> Leere Verzeichnisse und Dateien anlegen Abbildung 49: Übergabe von Aufrufargumenten an ein Konsolenprogramm Ein.\\neuesVerzeichnis\\neuesUnterverzeichnis"). z. dass der Befehlszeileninterpreter von Windows eine falsche Zahl an Argumenten übergibt. die zuvor sicherstellt. boolean status = f. z. müssen allerdings die entsprechenden Verzeichnisse bereits existieren. File f = new File(".createNewFile(). Mittlerweile geht dies aber deutlich einfacher mit der File-Methode createNewFile(): File f = new File(". die es selbst noch nicht gibt. Falls man hierbei einen vollen Pfadnamen (wie im Beispiel) angibt. Es kann daher recht praktisch sein. die null Bytes schrieb. scheitert der Aufruf. leere Datei anlegen Das Anlegen einer leeren Datei war in den Anfangszeiten von Java recht umständlich.mkdir().mkdir(). leeres Verzeichnis anlegen Das Anlegen von leeren Verzeichnissen erfolgt einfach durch Aufruf der Methode File.B.\\temp\\leereDatei. import java. // liefert nun true Neue. Leere Verzeichnisse und Dateien anlegen Neues. eine eigene Methode createNewFile() zu definieren.mkdir(). da man mit Hilfe einer Ausgabeklasse wie FileOutputStream eine explizite write()-Operation durchführen musste.B..*. Hier lauert allerdings eine kleine Falle: Wenn man einen Pfad angibt.io.mkdirs(). File f = new File(". boolean status = f. dass der Pfad an sich existiert: . gefolgt vom Schließen des Ausgabestreams mit close().\\meinVerzeichnis").

java) Ein. = new File(path). File f = new File(name). = p.und Ausgabe (IO) 233 import java. sonst false (Datei existert schon * oder keine Schreibrechte) */ public static boolean createNewFile(String name) { boolean result.pos).createNewFile().und Ausgabe . } catch(Exception e) { result = false. try { // zuerst mal so probieren result = f. erzeugt * * @param name relativer oder absoluter Dateiname * @return true bei Erfolg. Pfad wird ggf.printStackTrace(). result = false.substring(0. } } Listing 104: Methode zum sicheren Anlegen neuer Dateien (aus FileUtil. } return result. dass Pfad existiert und nochmal probieren int pos = name. class FileUtil { /** * Erzeugt eine neue leere Datei. if(pos >= String File p result 0) { path = name.mkdirs().>> Ein. } } } catch(Exception e) { e.*.separatorChar).createNewFile(). } try { if(result == false) { // sicherstellen. if(result) result = f.lastIndexOf(File.io.

Dateinamen (absolut und relativ). else System. Wurzel des Dateipfads und Zugriffsrechte: import java.*. Listing 106: FileInfo.java – Klasse zur Abfrage von Datei-/Verzeichniseigenschaften . /** * Klasse zur Ermittlung von Datei-/Verzeichniseigenschaften */ class FileInfo { private String fileName.234 >> Datei. public FileInfo(String name) { fileName = name.io.out.println("Leere Datei angelegt!"). Das nachfolgende Codeschnipsel zeigt ein Beispiel für das Ermitteln häufig benötigter Informationen wie Dateigröße. } catch(Exception e) { e.und Verzeichniseigenschaften abfragen Das Start-Programm zu diesem Rezept public class Start { public static void main(String[] args) { boolean result = FileUtil.und Verzeichniseigenschaften abfragen Neben einigen offensichtlichen Eigenschaften wie Dateinamen lassen sich über die Klasse java.printStackTrace().\\temp\\test.io. try { file = new File(name). import java.und Ausgabe } } Listing 105: Neue Datei anlegen 96 Datei. Ein.File weitere interessante Eigenschaften ermitteln.txt").createNewFile(". } } // Liefert true.exists().util.out. wenn die Datei existiert public boolean exists() { try { return file. if(result) System.Date. private File file.println("Datei konnte nicht erzeugt werden!").

for(File f : roots) { String path = f.B. } catch(Exception e) { e. if(file != null) result = file. } } // Liefert den Dateinamen ohne Pfad oder null bei Fehler public String getName() { if(file != null) return file. } Listing 106: FileInfo.listRoots().getCanonicalPath(). if(getAbsoluteName().printStackTrace(). else return null. } // Dateigröße in Bytes oder -1 bei Fehler public long getSize() { long result = -1.) Ein.getName().printStackTrace(). return result. return false.length(). d:\) für die aktuelle Datei // oder null bei Fehler public File getRoot() { try { File[] roots = File. return null.und Ausgabe (IO) 235 } catch(Exception e) { e. Pfad oder null bei Fehler public String getAbsoluteName() { try { return file.startsWith(path)) return f. else continue. } // Liefert das Wurzelverzeichnis (z.java – Klasse zur Abfrage von Datei-/Verzeichniseigenschaften (Forts.getCanonicalPath(). } } // Liefert den vollen Dateinamen inkl.und Ausgabe .>> Ein.

else return "". } // Liefert true.canWrite()) return "rw". } } Listing 106: FileInfo.) Ein.236 >> Datei. } // nichts gefunden -> Fehler return null. else if(file. return (new Date(time)). else return false. else return null.java – Klasse zur Abfrage von Datei-/Verzeichniseigenschaften (Forts. wenn es ein Verzeichnis ist public boolean isDirectory() { if(file != null && file.canRead()) return "r".printStackTrace().isDirectory()) return true.und Ausgabe . } // Liefert die Zugriffsrechte als "r" (lesen) oder "rw" ( // lesen und schreiben) oder "" (gar keine Rechte) public String getAccessRights() { if(file != null) { if(file.und Verzeichniseigenschaften abfragen } catch(Exception e) { e. } else return null.lastModified(). } // Liefert das Vaterverzeichnis oder null bei Fehler public File getParent() { if(file != null) return file. } // Liefert das Datum der letzten Änderung oder null bei Fehler public Date getLastModified() { if(file != null) { long time = file.getParentFile(). } else return null.

String suffix) lassen sich beliebig viele Dateien im Standard-Temp-Verzeichnis erzeugen2.deleteOnExit().println(tmp1.getAccessRights()).File spezielle Methoden an. Jede erzeugte Datei fängt dabei mit dem übergebenen String prefix an.out."). Listing 108: Temporäre Datei erzeugen 2. Ein. Unter Windows ist dies meist c:\Dokumente und Einstellungen\Username\Lokale Einstellungen\Temp. kann man sogar mit Hilfe der Methode deleteOn Exit() vorab festlegen.io.out. } } Listing 107: Dateieigenschaften ermitteln 97 Temporäre Dateien anlegen Temporäre Dateien. File tmp2 = File.getCanonicalPath()).createTempFile("daten_". gefolgt von einer automatisch erzeugten.txt").txt").out. Die Java-Bibliothek bietet jedoch in der Klasse java.getCanonicalPath()). System.>> Ein.".". mit denen sich der Einsatz von temporären Dateien etwas vereinfachen lässt. fortlaufenden Zahl und dem übergebenen String suffix als Dateiendung.println(tmp2. dass diese Dateien beim Beenden der Virtual Machine automatisch gelöscht werden und kein unnötiger Datenmüll zurückbleibt: import java. System.tmpDir). tmp2. Da temporäre Dateien nach Programmende nicht mehr benötigt werden. Mit createTempFile(String prefix. // Dateien verwenden System. // die andere Datei im aktuellen Verzeichnis erzeugen File tmpDir = new File(".io. public class Start { public static void main(String[] args) { FileInfo fi = new FileInfo("test.txt". public class Start { public static void main(String[] args) { try { // eine Datei im Standard-Temp Verzeichnis erzeugen File tmp1 = File.deleteOnExit(). die nur vorübergehend während der Programmausführung benötigt werden.createTempFile("daten_". also Dateien. tmp1.und Ausgabe (IO) 237 Das Start-Programm demonstriert Aufruf und Verwendung.println("Zugriffsrechte: " + fi.und Ausgabe .*. lassen sich natürlich als ganz normale Dateien (beispielsweise wie in Rezept 95 gezeigt) anlegen und einsetzen.

) 98 Ein.includeDirNames)).length. bei einem Verzeichnis ruft sich die Methode selbst mit diesem Verzeichnis als Argument auf. i++) { if(fileList[i]. } Listing 109: Methode zur rekursiven Auflistung von Verzeichnisinhalten . result. Hierfür eignet sich ein rekursiver Ansatz. class FileUtil { /** * Auflistung aller Dateien/Verzeichnisse in einem Startverzeichnis * und in allen Unterzeichnissen * * @param rootDir File-Objekt des Startverzeichnisses * @param includeDirNames Flag. boolean includeDirNames) { ArrayList<File> result = new ArrayList<File>().isDirectory() == true) { if(includeDirNames) result. Bei einer Datei wird der Name gespeichert.*.addAll(listAllFiles(fileList[i]. for(int i = 0.add(fileList[i]).add(fileList[i]).*. } } } Listing 108: Temporäre Datei erzeugen (Forts.util.238 >> Verzeichnisinhalt auflisten } catch(Exception e) { e. import java. import java.printStackTrace().und Ausgabe Verzeichnisinhalt auflisten Ein häufiges Problem ist das Durchlaufen aller Dateien und Unterverzeichnisse von einem gegebenen Wurzelverzeichnis aus. } else result.io. try { File[] fileList = rootDir. i < fileList.listFiles(). alle darin enthaltenen Dateien und Verzeichnisse auflistet und diese dann der Reihe nach durchgeht. ob auch reine Verzeichnisse als separater * Eintrag erscheinen (true/false) * @return ArrayList<File> mit allen File-Objekten */ public static ArrayList<File> listAllFiles(File rootDir. bei dem eine Methode listAllFiles() als Parameter ein Verzeichnis erhält.

B. } } } Listing 110: Verzeichnisinhalt auflisten 99 Dateien und Verzeichnisse löschen Zum Löschen einer Datei bzw. Voraussetzung für ein erfolgreiches Löschen ist eine Schreibberechtigung auf die gewünschte Datei für den Benutzer.) import java. if(st == true) System. Bei Verzeichnissen kommt eine weitere. } } Listing 109: Methode zur rekursiven Auflistung von Verzeichnisinhalten (Forts.getCanonicalPath()).io.").println("Datei geloescht").und Ausgabe (IO) 239 } catch(Exception e) { e.printStackTrace(). } return result.*. eines Verzeichnisses kann man die aus der Klasse File bekannte Methode delete() verwenden. z. } catch(Exception e) { e. boolean st = f. public class Start { public static void main(String[] args) { File root = new File(".out.out.util.>> Ein.\\temp\\test. File f = new File(".txt").*. .printStackTrace(). false). ArrayList<File> files = FileUtil.println(f. unter dessen Kennung das Java-Programm ausgeführt wird.delete(). import java.listAllFiles(root. oft lästige Bedingung hinzu: Das zu löschende Verzeichnis muss leer sein! In der Praxis ist dies natürlich meist nicht der Fall und man muss erst dafür Ein.und Ausgabe Das Start-Programm demonstriert den Gebrauch. try { for(File f : files) System.

i < fileList. } } // Datei/Verzeichnis löschen boolean status = startFile. boolean statusRecursive = true. } } catch(Exception e) { e.240 >> Dateien und Verzeichnisse löschen sorgen. if(startFile.und Ausgabe Das Start-Programm demonstriert den Aufruf: public class Start { public static void main(String[] args) { Listing 112: Datei/Verzeichnis löschen . Hierzu kann man den rekursiven Ansatz aus Rezept 98 einsetzen: import java. dass alle enthaltenen Dateien und Unterverzeichnisse gelöscht worden sind.printStackTrace(). i++) { boolean st = deleteFile(fileList[i]).length.listFiles().delete().isDirectory() == true) { // rekursiv den Inhalt löschen try { File[] fileList = startFile. return (status && statusRecursive). class FileUtil { /** Löscht die übergebene Datei oder Verzeichnis * (auch wenn es nicht leer ist) * * @param Zu löschende Datei/Verzeichnis * @return true bei vollständigem Löschen. for(int i = 0.io. if(st == false) statusRecursive = false. sonst false */ public static boolean deleteFile(File startFile) { if(startFile == null) return true. statusRecursive = false. } } Listing 111: Methode zum rekursiven Löschen von Verzeichnissen Ein.*.

Die höchste Kopiergeschwindigkeit erhält man.util.und Ausgabe .out. i < fileList.*. ob es sich um Binärdaten oder Text handelt. wenn auf Betriebssystemebene mit direktem Kanaltransfer gearbeitet wird. try { File[] fileList = rootDir. Beim Kopieren einer Datei spielt es übrigens keine Rolle.nio.. so dass man hier selbst programmieren muss.out. Die nachfolgende Klasse FileCopy zeigt eine mögliche Implementierung zum Kopieren von Dateien (Methode copyFile() oder ganzen Verzeichnissen (copyTree()) inklusive Unterverzeichnissen: import java. for(int i = 0. man kann immer mit einer Instanz von FileInputStream zum Lesen und FileOutputStream zum Schreiben arbeiten.channels.) 100 Dateien und Verzeichnisse kopieren Für das Kopieren von Dateien und Verzeichnissen gibt es keine direkte Java-Methode.*. if(result) System.listFiles().io. import java.println("Datei/Verzeichnis geloescht").println("Konnte nicht loeschen!").\\testdir"). import java. boolean includeDirNames) { ArrayList<File> result = new ArrayList<File>().h. else System. boolean result = deleteFile(f).>> Ein. Diese Funktionalität wird durch die Methode transferTo() der Klasse FileInputStream ermöglicht.length. d. class FileCopy { /** * Ausgabe aller Datei-/Verzeichnisnamen in einem Startverzeichnis und in * allen Unterzeichnissen * * @param rootDir File-Objekt des Startverzeichnisses * @param includeDirNames Flag.und Ausgabe (IO) 241 File f = new File(". i++) { Listing 113: Methoden zum Kopieren von Dateien und Verzeichnissen Ein.*. } } Listing 112: Datei/Verzeichnis löschen (Forts. ob auch Verzeichnisnamen als separater * Eintrag erscheinen (true/false) * @return ArrayList<File> mit allen File-Objekten */ public static ArrayList<File> listAllFiles(File rootDir.

String targetRoot) { boolean result.mkdir(). if(target. dass Unterverzeichnis vorhanden ist String targetRootName = root.add(fileList[i]). } /** * Kopieren einer Datei/Verzeichnisses. eine vorhandene Zieldatei * wird überschrieben * * @param sourceDir Name des zu kopierenden Verzeichnisses * @param targetRoot Name des Zielverzeichnisses.und Ausgabe Listing 113: Methoden zum Kopieren von Dateien und Verzeichnissen (Forts.add(fileList[i]). } } catch(Exception e) { e.isDirectory() == true) { if(includeDirNames) result. if(source.isDirectory() == false) return false.) .242 >> Dateien und Verzeichnisse kopieren if(fileList[i]. if(st == false) return false.separator + source. if(root. File root = new File(targetRoot).exists() == false) { boolean st = target. } return result. } Ein.includeDirNames)). ansonsten false */ public static boolean copyTree(String sourceDir.printStackTrace(). // sicherstellen. } else result.exists() == false || source.addAll(listAllFiles(fileList[i].exists() == false || root.getCanonicalPath() + File.getName().isDirectory() == false) return false. try { File source = new File(sourceDir). File target = new File(targetRootName). in das hineinkopiert * werden soll (muss existieren) * @return true bei Erfolg. result.

if(st == false) result = false.exists() == false) { boolean st = t.) Ein.und Ausgabe . } /** * Kopieren einer Datei. int pos = fullName. } boolean st = copyFile(f.indexOf(sourceDir).printStackTrace(). String targetName = targetRootName + subName. } continue. if(f. if(t. if(st == false) result = false. eine vorhandene Zieldatei * wird überschrieben * * @param sourceFile Name der Quelldatei * @param targetFile Name der Zieldatei * @return true bei Erfolg. ansonsten false */ public static boolean copyFile(String sourceFile. String subName = fullName. result = false.mkdir(). } return result. String targetFile) { boolean result.substring(pos + sourceDir. result = true. anlegen File t = new File(targetName). targetName).getCanonicalPath().und Ausgabe (IO) 243 // Auflistung aller zu kopierenden Dateien ArrayList<File> fileNames = listAllFiles(source.length()+1).>> Ein. for(File f : fileNames) { String fullName = f.getCanonicalPath().isDirectory()) { // Unterverzeichnis ggf. true). try { // Eingabedatei öffnen Listing 113: Methoden zum Kopieren von Dateien und Verzeichnissen (Forts. } } catch(Exception e) { e.

FileChannel output= outputFile. // die Länge der zu kopierenden Datei long num = input.size(). else System.printStackTrace(). result = true. result = false.out. Denken Sie daran.close(). die Pfade für sourceDir und targetRootDir anzupassen).println("Fehler beim Kopieren!"). // kopieren input. targetRootDir).copyTree(sourceDir. FileChannel input= inputFile. // Zieldatei öffnen FileOutputStream outputFile = new FileOutputStream(targetFile).und Ausgabe input.) Das Start-Programm demonstriert den Aufruf. } } Listing 114: Datei/Verzeichnis kopieren .244 >> Dateien und Verzeichnisse kopieren FileInputStream inputFile= new FileInputStream(sourceFile). } } Listing 113: Methoden zum Kopieren von Dateien und Verzeichnissen (Forts.println("Verzeichnis kopiert!"). Quell.transferTo(0. String targetRootDir ="c:\\UserDirs". Ein. boolean result = FileCopy.output).getChannel(). output. public class Start { public static void main(String[] args) { String sourceDir = "c:\\temp\\MyDir".num. } return result.und Zielverzeichnis vor dem Aufruf anzulegen (bzw. } catch(Exception e) { e. if(result) System.close().out.getChannel().

dass bei einer Binärdatei die einzelnen Bytes ohne weitere Interpretation in den Speicher geladen werden.io.File bereits alles. public class Start { public static void main(String[] args) { Ein. was man braucht. schreibt einen String als Textdatei: . } catch(Exception e) { e.renameTo(newFile). in welcher Zeichenkodierung die Datei ursprünglich geschrieben worden ist.B. Die Zieldatei/das Verzeichnis darf noch nicht existieren. 102 Textdateien lesen und schreiben Beim Einlesen von Dateien muss man prinzipiell unterscheiden.und Ausgabe try { // ein Verzeichnis umbenennen File file = new File("c:\\temp\\appconfig"). c:\test.println("Verschieben erfolgreich: " + status).txt). Hierfür bietet die Klasse java. } } } Listing 115: Datei/Verzeichnis verschieben Beim Einsatz von renameTo() sollten Sie Folgendes beachten: Die Schreibrechte auf dem Dateisystem müssen vorhanden sein.txt nach d:\test.io. Das Verschieben über Partitionen hinweg ist unter Windows nicht möglich (z. boolean status = file. System. Aus technischer Sicht besteht der Unterschied darin. Für das korrekte Verarbeiten von Textdateien ist es daher wichtig zu wissen.out.printStackTrace(). während bei Textdaten ein oder mehrere aufeinander folgende Bytes zu einem Textzeichen (je nach verwendeter Zeichenkodierung) zusammengefasst werden.und Ausgabe (IO) 245 101 Dateien und Verzeichnisse verschieben/umbenennen Das Verschieben von Dateien und Verzeichnissen ist wesentlich einfacher als das Kopieren. ob es sich um Binärdaten oder Textdaten handelt. File newFile = new File("c:\\temp\\basics"). in Form einer Methode renameTo(): import java. da technisch gesehen nur der Name geändert werden muss.>> Ein. Das folgende Beispiel liest eine Datei in der gewünschten Kodierung als String ein bzw.*.

UTF-8 oder ISO-8859-1.printStackTrace(). int c. import java.util. else reader = new InputStreamReader(new FileInputStream(fileName)). } in. return buffer. class FileUtil { Ein.und Ausgabe /** * Lädt eine Textdatei mit der angegebenen Zeichenkodierung * * @param fileName Name der Datei * @param charSet Name der Zeichenkodierung. * bei Angabe von null wird die Default-Kodierung der * Virtual Machine genommen * @return String-Objekt mit eingelesenem Text oder null bei * Fehler */ public static String readTextFile(String fileName. StringBuilder buffer = new StringBuilder().246 >> Textdateien lesen und schreiben /** Listing 116: Methoden zum Lesen und Schreiben von Textdateien beliebiger Zeichenkodierung * * @author Peter Müller */ import java.toString().B. charSet).*. if(charSet != null) reader = new InputStreamReader(new FileInputStream(fileName).*. z. } return result. String charSet) { String result = null. } catch(Exception e) { e. result = null.read()) >= 0) { buffer. BufferedReader in = new BufferedReader(reader).close(). while((c = in. try { InputStreamReader reader. } .append((char) c).io.

\\john_maynard_utf8. BufferedWriter out = new BufferedWriter(writer).>> Ein. } } Listing 116: Methoden zum Lesen und Schreiben von Textdateien beliebiger Zeichenkodierung (Forts.txt". data.writeTextFile(text. out.write(data. Listing 117: Textdatei lesen/schreiben Ein.\\john_maynard. . public class Start { public static void main(String[] args) { String text = FileUtil. 0. charSet). "UTF-8"). } return result.) Das Start-Programm demonstriert den Aufruf. if(charSet != null) writer = new OutputStreamWriter(new FileOutputStream(fileName). String charSet) { boolean result = true. result = false. ".und Ausgabe /** * Schreibt einen String als Textdatei in der angegebenen Zeichenkodierung. "ISO-8859-1").close(). out.und Ausgabe (IO) 247 try { OutputStreamWriter writer. * * @param data Zu schreibender String * @param fileName Dateiname * @param charSet Zeichenkodierung (oder null für * Default-Zeichenkodierung der VM) * @return true bei Erfolg */ public static boolean writeTextFile(String data.txt".length()).readTextFile(". boolean status = FileUtil. String fileName.printStackTrace(). else writer = new OutputStreamWriter(new FileOutputStream(fileName)). } catch(Exception e) { e.

ein eigenes FileReader-Objekt zu erzeugen. // wenn Datei nicht existiert.und Ausgabe Die beiden Methoden String file2String(String filename) String file2String(FileReader in) lesen den Inhalt der Datei und liefern ihn als String zurück. mit dem Sie den Inhalt einer ASCII.read(bytesRead)) > 0) str.io. Die erste Version spart Ihnen die Mühe. int countBytes = 0. public static String file2String(String filename) throws IOException { // Versuche Datei zu öffnen .close(). In String-Form kann der Text dann beispielsweise mit den Methoden der Klasse String bearbeitet. countBytes). 0. Die Methoden wurden überladen. import java.) 103 Textdatei in String einlesen Und gleich noch ein Rezept. } . while( (countBytes = in. return str. return str. // Dateiinhalt in String lesen String str = file2String(in). Ein. damit Sie sie sowohl mit einem Dateinamen als auch mit einem FileReader-Objekt aufrufen können.IOException. mit regulären Ausdrücken durchsucht oder in eine Textkomponente (beispielsweise JTextArea) kopiert werden.io. ein Verzeichnis ist oder nicht gelesen // kann FileReader in = new FileReader(filename). // Stream schließen und String zurückliefern in. import java.oder ANSI-Textdatei in einen String einlesen können.Löst FileNotFoundException aus.248 >> Textdatei in String einlesen } } Listing 117: Textdatei lesen/schreiben (Forts. char[] bytesRead = new char[512]. } public static String file2String(FileReader in) throws IOException { StringBuilder str = new StringBuilder().append(bytesRead.toString().FileReader.

d.und StringBuilder-Objekte sind dagegen mutable (veränderlich) und werden direkt bearbeitet. da man sich hier im Gegensatz zu Textdaten keinerlei Gedanken über Zeichenkodierungen machen muss. import java. } /** Listing 118: Methoden zum Lesen und Schreiben von Binärdateien Ein. } return result.und Ausgabe (IO) 249 Um das Lesen des Dateiinhalts möglichst effizient zu gestalten..io. input. 0. für die Ausgabe empfiehlt sich BufferedOutputStream. Zur Eingabe bietet sich BufferedInputStream an.>> Ein.*. sondern in 512-Byteblöcken mit read(char[]) eingelesen.h. weil nicht threadsicher. } catch(Exception e) { e. input = new BufferedInputStream(new FileInputStream(fileName)). num). beim Konkatenieren oder anderen String-Manipulationen werden immer neue Strings angelegt und der Inhalt des alten Strings wird samt Änderungen in den neuen String kopiert. input. int num = input. StringBuffer. result = new byte[num]. Da gleichzeitige Zugriffe aus verschiedenen Threads auf die lokale Variable str nicht gegeben sind. Der Grund ist Ihnen sicherlich bekannt: Strings sind in Java immutable (unveränderlich).read(result. try { BufferedInputStream input.printStackTrace(). werden die Zeichen nicht einzeln mit read().close(). Außerdem werden die Zeichen nicht direkt an ein String-Objekt angehängt (etwa mit + oder concat()). result = null. .available(). haben wir für die obige Implementierung StringBuilder gewählt. sondern in einem StringBuffer gespeichert. class FileUtil { /** * Liest eine Binärdatei in ein byte-Array ein * * @param fileName Zu lesende Binärdatei * @return byte[] oder null bei Misserfolg */ public static byte[] readBinaryFile(String fileName) { byte[] result = null.und Ausgabe in der Ausführung ist. nur dass StringBuilder schneller 104 Binärdateien lesen und schreiben Der Umgang mit Binärdaten ist eigentlich sehr einfach. StringBuffer und StringBuilder sind nahezu wie Zwillinge.

RandomAccessFile. Sie arbeitet recht low-level. in die . } catch(Exception e) { e. die ggf.close().und Ausgabe Das Start-Programm demonstriert den Aufruf. } } Listing 118: Methoden zum Lesen und Schreiben von Binärdateien (Forts.writeBinaryFile(data. } return result.printStackTrace(). Man kann sich das am besten so vorstellen. FileUtil.pdf"). Dies betrifft insbesondere Textzeichen. * eine vorhandene Datei wird überschrieben * * @param data Zu schreibende Binärdaten * @param fileName Dateiname * @return true bei Erfolg */ public static boolean writeBinaryFile(byte[] data. d. dass die Datei ein byte-Array im Hauptspeicher ist und man auf jede Indexposition direkt zugreifen kann.) Ein. data.\\kopie.pdf"). Unterstützt wird der Random Access durch die Klasse java.\\windows_konsole. String fileName) { boolean result = true. output. result = false.250 >> Random Access (wahlfreier Zugriff) * Schreibt ein byte-Array als Binärdatei. try { BufferedOutputStream output. } } Listing 119: Binärdateien lesen/schreiben 105 Random Access (wahlfreier Zugriff) Beim so genannten wahlfreien Zugriff (Random Access) kann man in einer Datei den Schreib-/ Lesezeiger beliebig positionieren. output. um dann an dieser Position zu lesen oder zu schreiben.readBinaryFile(". public class Start { public static void main(String[] args) { byte[] data = FileUtil.length).io.h. 0. ". der Programmierer muss exakt wissen. wie viele Bytes ab dieser Position gelesen oder geschrieben werden sollen und was mit den Daten dann passieren soll.. output = new BufferedOutputStream(new FileOutputStream(fileName)).write(data. wo er den Schreib-/Lesezeiger positioniert.

java – eine Klasse für den Zugriff auf beliebige Positionen in einer Textdatei 3.und Ausgabe (IO) 251 richtige Zeichenkodierung umgewandelt werden müssen. } /** * Datei schließen Listing 120: RandomAccess. RandomAccessFile hat zwar auch eine Methode readUTF(). } catch(Exception e) { e. und somit in der Regel nicht brauchbar ist. UTF-8) muss man byteweise einlesen und die Konvertierung selbst durchführen3.B. } return result. * "rw" = lesen und schreiben * @return true bei Erfolg.und Ausgabe . /** * Klasse für den wahlfreien Zugriff auf eine Datei */ class RandomAccess { private RandomAccessFile file.>> Ein.*. Bei anderen Zeichenkodierungen (z. mode). writeString() zum Schreiben einer Zeichenkette oder readLine() zum Lesen einer ganzen Zeile.printStackTrace(). sonst false */ public boolean open(String mode) { boolean result = true. die leider ein java-spezifisches UTF-Format erwartet.io. private final short MAX_LINE_LENGTH = 4096. try { file = new RandomAccessFile(fileName. private String fileName. result = false. public RandomAccess(String name) { fileName = name. das nicht UTF-8-kompatibel ist. } /** * Datei für wahlfreien Zugriff öffnen * * @param mode Modus "r" = lesen. Es existiert in RandomAccessFile zwar auch eine auf den ersten Blick brauchbare Methode readLine() zum zeilenweisen Einlesen von Textdateien. import java. Diese ist aber erstens ungepuffert und liest somit sehr langsam und liefert zudem lediglich für normale ASCII-Zeichen korrekte Zeichen zurück. Das folgende Beispiel zeigt daher eine Klasse zur Durchführung von wahlfreiem Dateizugriff mit einigen verbesserten Methoden. Ein.B. z.

return -1. } catch(Exception e) { e. } catch(Exception e) { e.length(). return false.getFilePointer().printStackTrace(). sonst false */ public boolean close() { try { file.und Ausgabe } /** * Liefert die aktuelle Größe der Datei in Bytes oder -1 bei Fehler * * @return Anzahl Bytes */ public long getLength() { try { return file. } } /** * Liefert den aktuellen Wert des Dateizeigers * * @return long-Wert mit Position oder -1 bei Fehler */ public long getFilePointer() { try { return file. } } /** * Hängt die übergebenen Bytes an das Ende der Datei */ public void append(byte[] data) { Listing 120: RandomAccess.printStackTrace().close(). } Ein.java – eine Klasse für den Zugriff auf beliebige Positionen in einer Textdatei (Forts.printStackTrace(). return true.252 >> Random Access (wahlfreier Zugriff) * * @return true bei Erfolg. return -1. } catch(Exception e) { e.) .

} catch(Exception e) { e.seek(startPos). i < actual. . } } /** * Liest die angegebene Anzahl Bytes ein und liefert sie als Array zurück * * @param startPos Position. int num) { try { file.read(data.getBytes(encoding). for(int i = 0. byte[] data = new byte[num]. append(byteData). num).java – eine Klasse für den Zugriff auf beliebige Positionen in einer Textdatei (Forts.printStackTrace().und Ausgabe (IO) 253 try { file.) Ein. Listing 120: RandomAccess.seek(file.>> Ein. if(actual < num) { // das Array kleiner machen.write(data). int actual = file.printStackTrace(). data = tmp.und Ausgabe /** * Hängt den String in der gewünschten Kodierung an */ public void appendString(String str. String encoding) { try { byte[] byteData = str. } return data. da weniger als gewünscht gelesen // worden ist byte[] tmp = new byte[actual]. i++) tmp[i] = data[i]. } } } catch(Exception e) { e. 0.length()). file. ab der gelesen werden soll * @param num Anzahl einzulesender Bytes * @return byte[] mit eingelesenen Daten oder null bei Fehler */ public byte[] read(long startPos.

file. return true. } catch(Exception e) { e. return null.printStackTrace(). 0.java – eine Klasse für den Zugriff auf beliebige Positionen in einer Textdatei (Forts. } catch(Exception e) { e. long startPos) { try { file.write(data. data.getBytes(encoding).und Ausgabe . sonst false */ public boolean writeString(String data. } } Listing 120: RandomAccess.printStackTrace().) Ein. file. } } /** * Schreibt die übergebenen Bytes in die Datei * * @param data Position.seek(startPos). return true.write(byteData.254 >> Random Access (wahlfreier Zugriff) } catch(Exception e) { e. ab der geschrieben werden soll * @return true bei Erfolg.seek(startPos). byte[] byteData = data.printStackTrace(). sonst false */ public boolean write(byte[] data.length). return false. 0. return false. long startPos) { try { file. String encoding. byteData. ab der geschrieben werden soll * @param startPos Array mit zu schreibenden Daten * @return true bei Erfolg. } } /** * Schreibt den übergebenen String in der gewünschten Zeichenkodierung * als Bytefolge * * @param data zu schreibender String * @param encoding Name der Zeichenkodierung * @param startPos Startposition.length).

seek(startPos).read(buffer). pos). public class Start { public static void main(String[] args) { // Datei öffnen Listing 121: Wahlfreier Dateizugriff Ein. String encoding) { try { file. else result = null.und Ausgabe /** * Liest ab der angebenene Position einen String * bis zum ersten Auftreten von \n * String darf nicht länger als MAX_LINE_LENGTH Bytes enthalten * * @param startPos Startposition * @param encoding Zeichenkodierung * @return eingelesene Zeile (ohne \n) oder null bei Fehler */ public String readLine(long startPos.substring(0.printStackTrace(). encoding). return null. if(num > 0) { result = new String(buffer.java – eine Klasse für den Zugriff auf beliebige Positionen in einer Textdatei (Forts.) Das Start-Programm demonstriert die Verwendung der Klasse und ihrer Methoden.>> Ein. } catch(Exception e) { e. } return result. if(pos >= 0) result = result. .und Ausgabe (IO) 255 byte[] buffer = new byte[MAX_LINE_LENGTH]. } } } Listing 120: RandomAccess. // ab erstem Auftreten von '\n' abschneiden int pos = result. int num = file. String result = null.indexOf('\n').

channels.open("rw"). public RandomAccess(String name) { fileName = name. Listing 122: Methoden zum Sperren von Dateien . // zuletzt erzeugte Zeile wieder lesen String line = file.nio.txt")."ISO-8859-1"). private final short MAX_LINE_LENGTH = 4096.und Ausgabe file. Die in Rezept 105 gezeigte Klasse RandomAccess könnte daher folgendermaßen um eine geeignete lock()-Methode ergänzt werden: import java. falls unter Umständen mehrere Programme gleichzeitig auf einer Datei operieren können und ein Sperrmechanismus benötigt wird. import java.256 >> Dateien sperren RandomAccess file = new RandomAccess(". eine exklusive Sperre auf eine ganze Datei oder einen bestimmten Bereich zu erhalten. Betriebssystem-Prozess zugreifbar ist. bis die FileLock-Methode release() aufgerufen wird.getLength().println(line). Unter Java bietet die Klasse java. private String fileName. mit deren Hilfe man versuchen kann. System.FileChannel die Methode tryLock() an. bietet das Windows-Dateisystem (aber nicht Unix/Linux) das Konzept der Dateisperre.out.) Beachten Sie auch Rezept 106. // Position des alten Dateiendes speichern long pos = file. wird ein Objekt vom Typ FileLock zurückgegeben. 106 Dateien sperren Um dafür zu sorgen. Ein.nio. } } Listing 121: Wahlfreier Dateizugriff (Forts. Wenn eine Sperre erfolgreich war.close(). der definierte Abschnitt ist nun so lange gegen fremde Zugriffe gesperrt. /** * Klasse für den wahlfreien Zugriff auf eine Datei */ class RandomAccess { private RandomAccessFile file.*.appendString("Am Ende der Welt\n". file. dass eine Datei für eine gewisse Zeit nur für ein Programm bzw.channels. // String ans Ende anhängen file.\\Halley.io.*. Die Datei bzw. "ISO-8859-1").readLine(pos.

printStackTrace(). shared). return fc. import java. Ein.nio. boolean shared) { try { FileChannel fc = file.>> Ein.*. num.channels.getChannel(). } } // Rest wie in Rezept 105 } Listing 122: Methoden zum Sperren von Dateien (Forts. return null.tryLock().getChannel().tryLock(startPos. long num. Danach öffnet es die Datei ein zweites Mal und versucht über das zweite RandomAccess-Objekt. return null.) Das Start-Programm öffnet eine Datei zum Schreiben und sperrt sie. } catch(Exception e){ e.printStackTrace(). } } /** * Setzt eine Sperre auf einem Dateiabschnitt * * @param startPos Startposition in der Datei * @param num Größe des Sperrbereichs (Anzahl Bytes) * @param shared Shared-Flag (true = andere Sperren dürfen überlappen) * @return FileLock-Objekt oder null bei Fehlschlag */ public FileLock lock(long startPos.und Ausgabe (IO) 257 } /** * Setzt eine Sperre auf der ganzen Datei * * @return FileLock-Objekt oder null bei Fehlschlag */ public FileLock lock() { try { FileChannel fc = file. public class Start { public static void main(String[] args) { Listing 123: Dateisperre . in die Datei zu schreiben. return fc.und Ausgabe } catch(Exception e) { e.

107 CSV-Dateien einlesen CSV-Dateien waren früher ein sehr beliebtes Mittel zum Austausch tabellarischer Daten zwischen Programmen und Betriebssystemen. file2.\\Halley. // Sperre freigeben try { lock.close(). Ein.release(). // Datei nochmals öffnen RandomAccess file2 = new RandomAccess(".\\Halley. "ISO-8859-1"). dass ein bestimmter Thread die Datei sperrt. // ganze Datei für andere sperren und etwas schreiben FileLock lock = file. "ISO-8859-1"). Heute wird zu diesem Zweck meist XML eingesetzt. file. aber es gibt immer noch zahlreiche Programme. gut portierbar) und dem einfachen Format begründet.appendString("\nDateiende\n".close().lock(). Es ist also nicht möglich.printStackTrace().txt"). um die anderen Threads am Schreiben zu hindern. die das CSV-Format unterstützen. Der Erfolg der CSV-Dateien liegt vor allem in der Abspeicherung als Textdatei (direkt lesbar.appendString("Text einschmuggeln\n".open("rw").258 >> CSV-Dateien einlesen // Datei öffnen RandomAccess file = new RandomAccess(". file2.open("rw").) Der Sperrmechanismus greift nur auf Prozessebene und nicht auf Threadebene. } catch(Exception e) { e. Die Grundregeln für den Aufbau einer CSV-Datei lauten: Jede Tabellenzeile (bzw. } file. } } Listing 123: Dateisperre (Forts. H in we is .txt"). file. Hierfür müssen Sie eine explizite Synchronisierung einrichten. da Datei gesperrt file2.und Ausgabe // Versuch zu schreiben // Löst Exception aus. die als CSV-Dateien vorliegen. wie es umgekehrt auch immer noch Datensammlungen gibt. Die Daten aus den einzelnen Spalten (Feldern) werden durch Kommata getrennt. Datensatz) entspricht einer Textzeile.

die in Anführungszeichen geklammert sind. ParseException { Vector<String[]> lines = new Vector<String[]>(). /** * Methode zum Einlesen und Parsen von CSV-Dateien */ public static Vector<String[]> readCSVFile(String filename.1". auch Zeilenumbruchzeichen.Florian Gerhard.Herr Listing 124: Beispiel für eine CSV-Datei.Süß. char delimiter) throws IOException. Kommentarzeilen findet man in CSV-Dateien eher selten. CSV-Dateien lesen – 1. (Die Collection speichert dann die einzelnen Zeilen – in Form von String-Arrays – und die String-Arrays speichern die Felder der jeweiligen Zeilen. werden sie meist mit einem einfachen Zeichen (# oder !) eingeleitet und erstrecken sich bis zum Zeilenende."333-5999.2".Bitter. BufferedReader in = new BufferedReader(new FileReader(filename))."333-6610. int countFields = -1. String[] fields = null.2". die keine Trennzeichen in den Werten enthalten.5". Listing 125: readCSVFile() parst CSV-Dateien. 4. Nutzen Sie dabei ruhig den Umstand. weswegen CSV oft auch als Akronym für Character Separated Values verstanden wird (ein prominentes Beispiel hierfür ist Microsoft Excel. können ohne große Mühe in einer einzigen while-Schleife gelesen und geparst werden. ist.Anrede 1.Herbert.Frau 2.) Die nachfolgend abgedruckte Methode MoreIO. Die Angabe der Spaltenüberschriften in der ersten Zeile ist weit verbreitet. die Datei mit einem BufferedReader zeilenweise einzulesen. aber ebenfalls nicht zwingend vorgeschrieben. so dass es etliche Abwandlungen gibt.Vorname. Allerdings ist das CSV-Format nicht standardisiert. die nicht übermäßig groß sind und bei denen das Trennzeichen nicht in den Werten auftaucht.Kundennummer.Salz. welches das Semikolon als Trennzeichen benutzt). int countLines = 1.Frau 4. . Alles. was Sie tun müssen. dass split() die Felder bereits als String-Array zurückliefert. und speichern Sie die String-Arrays einfach in einer dynamisch mitwachsenden Collection.Herr 3. String line. die Zeilen mit Hilfe der String-Methode split() in Felder zu zerlegen und Letztere in geeigneter Weise abzuspeichern. Die häufigste Variation ist der Austausch des Kommas durch ein anderes Trennzeichen.>> Ein. Ein.und Ausgabe (IO) 259 Taucht das Komma in einem Feldwert auf.Sauer."333-3001. Ansatz CSV-Dateien.Claudia Leonie. falls vorhanden.Maria. Manche Programme erlauben in Feldwerten.4 Dem Einsatz des Kommas als Trennzeichen verdankt das Format im Übrigen auch seinen Namen: Comma Separated Values."333-0134.und Ausgabe ID.readCSVFile() verfährt auf eben diese Weise. wird dieser in Anführungszeichen (") gesetzt.Name.

260 >> CSV-Dateien einlesen // Dateiinhalt zeilenweise lesen while( (line = in. (Leerzeilen werden zuvor ausgesondert. In einer einzigen while-Schleife werden die einzelnen Zeilen eingelesen.valueOf(delimiter)). ob die Anzahl vorgefundener Felder mit der Anzahl Felder in countFields übereinstimmt.length. die keine Trennzeichen in den Werten enthalten.und Ausgabe // Wenn erste Zeile.equals("") ) continue. Anschließend wird für alle geparsten Zeilen geprüft.length) { lines. // Felder aus Zeilen extrahieren fields = line. // Sicherstellen. Die Nummer der betroffenen Zeile (countLines) wird als Argument dem Exception-Konstruktor übergeben und kann vom aufrufenden Programm via getErrorOffset() abgefragt werden. Die Anzahl Felder in der ersten geparsten Zeile merkt sich die Methode in der lokalen Variable . } Listing 125: readCSVFile() parst CSV-Dateien. von Whitespace an den Enden befreit und dann mit Hilfe von split() in Felder aufgeteilt. wird eine ParseException ausgelöst.) countFields. ++countLines. Trifft die Methode auf eine Zeile mit abweichender Spaltenzahl. (Forts.readLine()) != null) { // Whitespace an Enden entfernen line = line. } else throw new ParseException("Ungleiche Anzahl Felder in " + "Zeilen der CSV-Datei". // Collection mit Feld-Arrays zurückliefern return lines. dass alle Zeilen die gleiche Anzahl Felder haben if (countFields == fields.) Die Methode readCSVFile() übernimmt als Argumente den Namen der CSV-Datei und das Trennzeichen.split(String. Ein. // Leerzeilen übergehen if (line.add(fields).close().trim(). countLines). } // Stream schließen in. dann Anzahl Felder abspeichern if (countFields == -1) countFields = fields.

was uns die Definition einer eigenen Parse-Methode ersparte. .')). } catch(ParseException e) { System.5 5 Der entscheidende Schritt beim Parsen von CSV-Dateien ist die Zerlegung der Zeilen in Felder. unterscheiden. bedarf es eines erweiterten Ansatzes mit eigener Parse-Methode. Doch split() ist zum Parsen von CSV-Zeilen nur mit Einschränkungen geeignet. können die Spalten in der von Start_MoreIO erzeugten Ausgabe verrutschen. } catch(IOException e) { System.println("FEHLER beim Parsen der Datei in Zeile " + e. liefert für leere Felder am Zeilenende (in der CSV-Datei folgt auf das letzte Trennzeichen nur noch Whitespace) keinen String zurück.readCSVFile("Dateiname". Die zu verarbeitenden CSV-Dateien dürfen also keine leeren Felder enthalten (zumindest keine am Zeilenende) und das Trennzeichen darf nicht in den Feldwerten auftauchen. denn die Methode kann nicht zwischen echten Trennzeichen und Trennzeichen. Um beliebige CSV-Dateien verarbeiten zu können. Achtung Achtung! Wenn in einer Spalte Feldwerte stark unterschiedlicher Breite stehen. Der Name der CSV-Datei und das Trennzeichen werden als Befehlszeilenargumente übergeben.getErrorOffset()). die innerhalb von Anführungszeichen stehen und daher als normale Zeichen anzusehen sind. '.err. } Listing 126: Aufruf von readCSVFile() Ein.println("FEHLER beim Oeffnen der Datei"). 5.err. mit Tabulatoren zwischen den Feldern aus.und Ausgabe Abbildung 50: Start_MoreIO parst eine CSV-Datei mit Hilfe von readCSVFile() und gibt den Inhalt zeilenweise. Im vorliegenden Ansatz haben wir hierfür die String-Methode split() herangezogen.und Ausgabe (IO) 261 try { Vector<String[]> lines = MoreIO.>> Ein.

.io.io. um die Zeile in Felder zu zerlegen. import import import import import import import java. String[] splitLine(String line. wenn eine weitere Zeile mit Daten vorhanden ist.FileReader.err. java. String[] nextLine() liefert ein String-Array mit den Feldern der nächsten Zeile zurück. . die zwischen Anführungszeichen stehen.util. java. java. bzw. CSVTokenizer.und Ausgabe } catch(IOException e) { System.text. Bei der Instanzierung übergeben Sie dem Konstruktor der Klasse wahlweise den Namen der zu parsenden CSV-Datei oder ein bereits erzeugtes Reader-Objekt sowie das Trennzeichen.nextLine()).println("FEHLER beim Oeffnen der Datei").getErrorOffset()). java.Vector. Wie der Name schon erahnen lässt. Statt die CSV-Datei wie readCSVFile() komplett einzulesen und zu parsen. CSVTokenizer csv = null. Ansatz Nach der »Quick-and-dirty«-Implementierung mit der Methode readCSVFile() wenden wir uns nun einer professionelleren Lösung zu. } catch(ParseException e) { System. char delimiter) wird intern von nextLine() aufgerufen..println("FEHLER beim Parsen der Datei in Zeile " + e.'). ist die Klasse CSVTokenizer an StringTokenizer angelehnt (jedoch nicht von dieser abgeleitet) und wird auch ganz ähnlich verwendet. } Die Klasse CSVTokenizer verfügt insgesamt über drei Methoden: boolean hasMoreLines() liefert true zurück. Die Methode überliest Trennzeichen..add(csv. wenn keine weiteren Zeilen verfügbar sind.err. mit denen die Datei zeilenweise eingelesen werden kann: hasMoreLines() und nextLine(). stellt die Klasse CSVTokenizer Methoden zur Verfügung..io. und liefert auch für leere Felder am Zeilenende einen LeerString ("") zurück. '.FileNotFoundException. } Ein.ParseException. java. Vector<String[]> lines = new Vector<String[]>().io. . einliest. null.io.BufferedReader.IOException.262 >> CSV-Dateien einlesen CSV-Dateien lesen – 2. java. while (csv. /** * Klasse zum Lesen und Parsen von CSV-Dateien */ .Reader. try { csv = new CSVTokenizer("Dateiname".hasMoreLines()) { lines. die CSV-Dateien mit Hilfe einer eigenen Klasse. die analog zu den StringTokenizer-Methoden hasMoreTokens() und nextToken() verwendet werden.

delimiter = delimiter.>> Ein. allerdings mit dem Unterschied. sondern die eigene Methode splitLine() aufgerufen wird.delimiter = delimiter. } Die Methode nextLine() gleicht der Methode readCSVFile() aus dem ersten Ansatz. /** * Liest die nächste Zeile lesen und speichert sie in nextLine * Liefert im Erfolgsfall true zurück und false.trim(). Ein. wenn keine Zeile mehr * verfügbar. String line. Gelingt dies. Leerzeilen werden übersprungen */ public boolean hasMoreLines() { if (nextLine == null) try { while( (nextLine = reader. } public CSVTokenizer(Reader reader.reader = new BufferedReader(new FileReader(filename)).readLine()) != null) { nextLine = nextLine.und Ausgabe . // Schleife beenden } } catch (IOException e) { } if (nextLine != null) return true. dass zum Zerlegen der Zeilen in Felder nicht die String-Methode split().equals("")) // Wenn nicht leere Zeile break. public int countLines = 0. char delimiter) { this. nicht leere Zeile aus der CSV-Datei zu lesen. private int countFields = -1. private char delimiter. public CSVTokenizer(String filename. this. this. if (!nextLine. } 263 Die Methode hasMoreLines() versucht eine weitere. /** * Liefert ein String-Array mit den Feldern der nächsten Zeile zurück */ public String[] nextLine() throws ParseException { String[] fields = null. speichert sie die Zeile in der Instanzvariablen nextLine und liefert true zurück.und Ausgabe (IO) public class CSVTokenizer { private BufferedReader reader. char delimiter) throws FileNotFoundException { this.reader = new BufferedReader(reader). private String nextLine = null. else return false.

length. int len = line. // Felder aus Zeile extrahieren fields = splitLine(nextLine. char delimiter) { Vector<String> fields = new Vector<String>(). end. werden ignoriert. dass alle Zeilen die gleiche Anzahl Felder haben if (countFields != fields. int i = 0. wird nach Durchlaufen der Zeile noch ein leerer String für das letzte Feld angehängt. delimiter). // Erstes Zeichen des Feldes quote = false. /** * Zeile in Felder zerlegen. dann Anzahl Felder abspeichern if (countFields == -1) countFields = fields. Trennzeichen.length(). countLines).length) { throw new ParseException("Ungleiche Anzahl Felder in " + "Zeilen der CSV-Datei". wird von getNextLine() aufgerufen */ private String[] splitLine(String line. } // nextLine zurück auf null setzen nextLine = null. wo sie das übergebene Trennzeichen (delimiter) vorfindet. die innerhalb von Anführungszeichen stehen.264 >> CSV-Dateien einlesen // Nächste Zeile in nextLine einlesen lassen if (!hasMoreLines()) return null. // // // // // // Anzahl Zeichen in Zeile aktuelle Indexposition aktuelles Zeichen Anfang und Ende des aktuellen Feldes wenn true. // Wenn erste Zeile. // Ende des aktuellen Feldes finden Listing 127: Die Klasse CSVTokenizer . dann befindet sich Delimiter in Anführungszeichen // Zeile Zeichen für Zeichen durchgehen while (i < len) { start = i. int start. return fields. // nur für Exception-Handling Ein. Endet die Zeile mit einem Trennzeichen. boolean quote. Die Teilstrings für die Felder werden in einer Vector-Collection gesammelt und zum Schluss in ein String-Array umgewandelt und zurückgeliefert. ++countLines. char c.und Ausgabe // Sicherstellen. } Die Methode splitLine() durchläuft die übergebene Zeile Zeichen für Zeichen und zerlegt sie an den Stellen.

substring(start.und Ausgabe (IO) 265 while (i < len) { c = line. end--. } // Wenn letztes Feld leer (Zeile endet mit Trennzeichen). // Vector-Collection als String-Array zurückliefern String[] type = new String[0].add(""). return fields.>> Ein.length()-1) == delimiter) fields. end)).charAt(start) == '"' && line.charAt(end-1) == '"') { start++. // leeren String einfügen if (line. // Eventuelle Anführungszeichen am Anfang und am Ende verwerfen if (line. // Wenn c gleich dem Begrenzungszeichen und quote gleich false // dann Feldende gefunden. } } Listing 127: Die Klasse CSVTokenizer (Forts.und Ausgabe } end = i. i++. if (c == delimiter && quote == false) break. } // Feld speichern fields.add(line.) . // Letztes Zeichen des Feldes Ein.charAt(i). i++. // Im Falle eines Anführungszeichen quote umschalten if (c == '"') quote = !quote.charAt(line.toArray(type).

Die XML-Datei wird von dem Programm neu angelegt. beispielsweise: java Start kunden. Der Name der CSV-Datei und das Trennzeichen werden als Befehlszeilenargumente übergeben. denen wiederum die Knoten für die Felder untergeordnet sind.Bitter.Claudia.Florian.1".Sauer. wird sie überschrieben. Achtung! Wenn in einer Spalte Feldwerte stark unterschiedlicher Breite stehen.Herr Listing 128: Kunden."333-6610.csv 6. Da bietet es sich an.und Ausgabe Abbildung 51: Start_CSVTokenizer parst eine CSV-Datei mit Hilfe der Klasse CSVTokenizer und gibt den Inhalt zeilenweise. Ist die Datei bereits vorhanden.csv kunden. wird heutzutage meist XML anstelle von CSV für den elektronischen Datenaustausch verwendet. Die CSV-Datei darf keine Zeilenumbrüche in den Feldwerten enthalten und in der ersten Zeile sollten Spaltenüberschriften stehen.Vorname. dass die Spaltenüberschriften keine Leerzeichen enthalten dürfen). Das folgende Programm liest mit Hilfe der CSVTokenizer-Klasse aus Rezept 107 eine CSV-Datei ein. Aufgerufen wird das Programm mit dem Namen der CSV-Datei.xml .2"."333-5999.Kundennummer.2".Salz.Anrede 1. Der oberste Knoten der XML-Datei lautet <csvimport>.Maria. parst den Inhalt und schreibt ihn in eine XML-Datei. können die Spalten in der von Start_MoreIO erzeugten Ausgabe verrutschen. Für die folgende CSV-Datei ID. Altbestände von CSV-Dateien nach XML zu konvertieren.Herbert."333-3001. mit Tabulatoren zwischen den Feldern aus.266 >> CSV-Dateien in XML umwandeln 6 Ein.6 108 CSV-Dateien in XML umwandeln Wie bereits eingangs des vorangehenden Rezepts erwähnt."333-0134. .Frau 2. denn das Programm verwendet die Strings der ersten Zeile als Namen für die XML-Tags (woraus des Weiteren folgt. Darunter folgen die Knoten für die einzelnen Zeilen (<row>).Herr 3.Name.5".Frau 4.Suess. dem Namen der anzulegenden XML-Datei und dem Trennzeichen.

java. import import import import import java. // CSV-Datei öffnen csv = new CSVTokenizer(args[0]. String[] fields = null.newLine().2</Kundennummer> <Name>Salz</Name> <Vorname>Maria</Vorname> <Anrede>Frau</Anrede> </row> <row> .Vector.println(" Aufruf: Start <Dateiname> " + "<Dateiname> <Trennzeichen>").length != 3) { System. java. args[2]. BufferedWriter out. CSVTokenizer csv = null..charAt(0)).io..util. String[] header = null.und Ausgabe . // Überschriften einlesen und als XML-Knoten verwenden Listing 129: Programm zur Umwandlung von CSV-Dateien in XML Ein.text. System.und Ausgabe (IO) 267 würde das Programm also beispielsweise folgende Knotenstruktur anlegen: <?xml version="1. out.newLine(). out.out. out.write("<csvimport>")..io.io.exit(0).0" encoding="ISO-8859-1"?> <csvimport> <row> <ID>1</ID> <Kundennummer>333-3001.FileWriter.IOException. } try { // XML-Datei anlegen out = new BufferedWriter(new FileWriter(args[1])). out.ParseException. if (args.0\" encoding=\"ISO-8859-1\"?>"). Für eine Erklärung der Klasse CSVTokenizer siehe Rezept 107. java. java.BufferedWriter. <csvimport> Im Folgenden sehen Sie den vollständigen Quelltext.write("<?xml version=\"1. public class Start { public static void main(String args[]) { Vector<String[]> lines = new Vector<String[]>().>> Ein.

write("\t\t <" + header[i] + ">"). } } } Listing 129: Programm zur Umwandlung von CSV-Dateien in XML (Forts.err. out. out. out. } catch(ParseException e) { System. } // XML-Datei abschließen out. i < header.length.write("\t </row>"). // Felder als XML-Knoten ausgeben for(int i = 0.268 >> CSV-Dateien in XML umwandeln header = csv. } out.nextLine().write("\t <row>").getErrorOffset()).und Ausgabe . ++i) { out.newLine().println("FEHLER beim Parsen der Datei in Zeile " + e. out.nextLine(). // Zeilen einlesen und als XML-Knoten ausgeben while (csv.hasMoreLines()) { out.) Ein.write("</csvimport>").write(fields[i]). } catch(IOException e) { System. out.newLine().println("FEHLER beim Oeffnen der Datei").err.newLine().write("</" + header[i] + ">").newLine(). fields = csv.close(). out. out.

import java. komprimierte Dateien aus einem ZIP-Archiv auszulesen7. die dieses Manko behebt: import java. Jede Datei in einem ZIP-Archiv wird dabei durch eine Instanz einer besonderen Klasse ZipEntry repräsentiert.zip bietet Java die Möglichkeit.zip. class ZipArchive { private ZipFile myZipFile = null. import java. Für den normalen Hausgebrauch ist es bei ZipFile ein wenig lästig.util.*.util.und Ausgabe (IO) 269 Abbildung 52: Die für Kunden.>> Ein.io. Das nachfolgende Beispiel bietet eine Wrapper-Klasse.cvs erzeugte XML-Datei im Internet Explorer 109 ZIP-Archive lesen Mit der Klasse ZipFile aus dem Paket java.und Ausgabe .util. Das Erzeugen von ZIP-Archiven ist zurzeit nicht möglich! Ein.*. dass auch für jedes enthaltene Verzeichnis ein ZipEntryObjekt angelegt wird. was den »Programmierfluss« etwas hemmt – schließlich ist man ja in der Regel nur an den eigentlichen Dateien interessiert. Listing 130: ZIP-Archiv lesen 7.*.

und Ausgabe Listing 130: ZIP-Archiv lesen (Forts.) . /** * Konstruktor * * @param str Name des Archivs */ ZipArchive(String str) { myZipFileName = str.hasMoreElements()) { ZipEntry ze = (ZipEntry) e.entries(). if(ze.isDirectory() == false) // Verzeichnisse überspringen entries. result = false. } /** * Liefert ein Array mit den im Archiv enthaltenen Dateien * (außer Verzeichnisse) als ZipEntry-Objekte * * @return ZipEntry[] */ public ZipEntry[] getZipEntries() { ArrayList<ZipEntry> entries = new ArrayList<ZipEntry>(). try { myZipFile = new ZipFile(myZipFileName). false bei Fehler */ public boolean open() { boolean result.add(ze).270 >> ZIP-Archive lesen private String myZipFileName. while(e. } /** * Öffnet das Archiv zum Lesen * * @return true bei Erfolg. } return result. } catch(Exception e) { e. } } Ein. result = true.nextElement().printStackTrace(). if(myZipFile != null) { Enumeration e = myZipFile.

String[] result = new String[num]. } /** * Liefert ein Array mit den im Archiv enthaltenen Dateinamen *(außer Verzeichnisse) * * @return String[] */ public String[] getFileNames() { ZipEntry[] entries = getZipEntries(). } catch(Exception e) { e. int num = entries. return entries. i < num.>> Ein. } } Listing 130: ZIP-Archiv lesen (Forts.toArray(result).und Ausgabe (IO) 271 ZipEntry[] result = new ZipEntry[0]. } /** * Liefert den Inhalt der angegebenen Datei als unkomprimierten Datenstrom * * @param ze gewünschte Datei als ZipEntry-Objekt * @return Datenstrom als InputStream */ public InputStream getInputStream(ZipEntry ze) { InputStream result = null.getInputStream(ze). } return result. try { if(myZipFile != null) result = myZipFile. return result.printStackTrace().und Ausgabe .getName().length. i++) result[i] = entries[i]. for(int i = 0.) Ein.

util. byte[] buffer = new byte[num]. um einen Eingabestream zu erhalten. class ZipCreator { private String archiveName. Für jede Datei.und Ausgabe ZipArchive za = new ZipArchive("test. ZipEntry[] entries = za.read(buffer). import java. public class Start { public static void main(String[] args) { Ein.getZipEntries().B.*.available().*. den man dann zum Lesen verwenden kann. System.zip"). z. } } } Listing 131: Datei aus ZIP-Archiv lesen 110 ZIP-Archive erzeugen Das Erzeugen eines ZIP-Archivs ist nur unwesentlich aufwändiger als das Auslesen.getInputStream(entries[0]).open(). /** * Konstruktor * * @param voller Name des zu erzeugenden Archivs (inkl.println(str).util.*. in. die zusammen mit den Bytes der Datei einem Objekt vom Typ ZipOutputStream übergeben wird.io. InputStream in = za.io. die hinzugefügt werden soll. private ZipOutputStream outputStream = null.zip. String str = new String(buffer).printStackTrace().: import java.zip. import java. benötigt man eine ZipEntry-Instanz. } catch(Exception e) { e. ZIP-Endung) */ Listing 132: ZIP-Archiv erzeugen .*.out. try { int num = in. import java.272 >> ZIP-Archive erzeugen Wenn man eine Datei des ZIP-Archivs – repräsentiert durch ein entsprechendes ZipEntryObjekt – nun tatsächlich dekomprimiert auslesen will. za. muss man lediglich die Methode getInputStream() aufrufen.

} /** * leeres ZIP-Archiv erzeugen */ public void create() throws IOException { outputStream = new ZipOutputStream(new FileOutputStream(archiveName)). } .lastModified()).und Ausgabe /** * ZIP-Archiv schließen */ public void close() throws IOException { outputStream. // Datei lesen und dem Archiv komprimiert hinzufügen FileInputStream fis = new FileInputStream(f).DEFLATED).close().) Ein. BufferedInputStream bis = new BufferedInputStream(fis). zipEntry.write(buffer. } /** * Datei komprimieren und hinzufügen * * @return true bei Erfolg */ public boolean add(File f) { boolean result = true. zipEntry. try { // ZipEntry anlegen String name = f. while ((num = bis.read(buffer)) >= 0) { outputStream.closeEntry(). 0. long len = f.putNextEntry(zipEntry).close(). byte[] buffer = new byte[2048]. outputStream.length().und Ausgabe (IO) 273 public ZipCreator(String name) { archiveName = name. outputStream.setSize(len).getCanonicalPath(). zipEntry.setTime(f. int num. } catch(Exception e) { Listing 132: ZIP-Archiv erzeugen (Forts. num). } bis.setMethod(ZipEntry.>> Ein. ZipEntry zipEntry = new ZipEntry(name).

import org.5.apache.und Ausgabe Erstaunlich populär ist das Tabellenformat Excel zur Anzeige von Tabellendaten. die Sie von einem der zahlreichen Apache-Server herunterladen können. (Zum Beispiel http://apache.*.autinity.err.*. ob bestimmte Daten als Excel-Datei erzeugt werden können.java – Klasse zum Lesen und Schreiben von Excel-Dateien 8.io. mit denen die gewünschte ExcelStruktur zusammengebaut werden kann.hssf.) 111 Excel-Dateien schreiben und lesen Ein. Excel-Dateien bestehen aus Workbooks.*. .usermodel.1-final-20040804. Dies ist eigentlich eine sehr aufwändige Forderung.5. Für all diese Objekte und viele weitere bietet die POI-Bibliothek entsprechende Klassen an. Jedes Blatt entspricht einer Tabelle. import org.apache.poi. die in Arbeitsblätter unterteilt sind. import javax. import java. Aus dem ZIP-Archiv ist die jar-Datei poi-2. Datei poi-bin-2. result = false.zip. Das folgende Beispiel zeigt eine einfache Implementierung für den häufigen Fall. umgekehrt eine Excel-Datei einlesen möchte (pro Arbeitsblatt ein JTable-Objekt): /** * * @author Peter Müller */ import java. class Excel { /** * Schreibt eine Tabelle als Excel Datei.*.poifs.274 >> Excel-Dateien schreiben und lesen System. dass man den Inhalt einer JTable inklusive Spaltennamen in eine Datei ausgeben bzw.util. bestehend aus Zeilen/Spalten mit Zellen. } return result. } } Listing 132: ZIP-Archiv erzeugen (Forts.filesystem. Der Zeitstempel im Dateinamen wird wahrscheinlich ein anderer sein.poi.1-final. Programmierer werden daher häufig mit der Anfrage konfrontiert.de/jakarta/poi/release/bin. alle Zellen werden als String * interpretiert * * @param table Tabelle mit den Daten * @param fileName Dateiname Listing 133: Excel.swing. aber glücklicherweise gibt es eine recht brauchbare OpenSource-Implementierung im Apache-Jakarta-Projekt namens POI.*.jar8 zu extrahieren und der Name (inklusive jar-Endung) in den CLASSPATH der Java-Anwendung aufzunehmen).println(e).

createRow((short) i+1).setCellType(HSSFCell.length() > maxWidth[i]) maxWidth[i] = (short) name.getColumnName(i). j < colNum.BOLDWEIGHT_BOLD).>> Ein.setCellStyle(style). HSSFFont font = workBook. String sheetName) { boolean result = false. try { int colNum = table. HSSFSheet sheet = workBook. String fileName.setFont(font). j++) { cell = row.createSheet(sheetName). // für max. i++) { cell = row.length() > maxWidth[j]) maxWidth[j] = (short) value.length(). // Fettdruck für erste Zeile mit Spaltennamen HSSFCellStyle style = workBook.getRowCount(). cell.createRow((short) 0). // +1 wegen 0.createFont().getValueAt(i. HSSFRow row = sheet. String name = table. Zeile = Namen for(int j = 0. font.CELL_TYPE_STRING). cell. i < rowNum.) . int rowNum = table. short[] maxWidth = new short[colNum]. Ein.getColumnName(i)).createCell((short) i).und Ausgabe if(name. if(value. cell. style.setCellType(HSSFCell. i < colNum. HSSFCell cell.und Ausgabe (IO) 275 * @param sheetName Arbeitsblatt-Name * @return true bei Erfolg */ public boolean writeFile(JTable table. Spaltenbreite // Zeile 0 mit Spaltennamen for(int i = 0.j). cell. HSSFWorkbook workBook = new HSSFWorkbook().getColumnCount().CELL_TYPE_STRING). Listing 133: Excel.setCellValue(table.createCellStyle(). String value = (String) table. i++) { row = sheet.setBoldweight(HSSFFont.length(). } // übrige Zeilen mit den Daten for(int i = 0.createCell((short) j).java – Klasse zum Lesen und Schreiben von Excel-Dateien (Forts.

getNumberOfSheets(). // vorhandene Zeilen int startRow = sheet.write(out). ob erste Zeile als Spaltennamen * verwendet werden soll * @return ArrayList mit JTable pro Arbeitsblatt oder * null bei Fehler */ public ArrayList<JTable> readFile(String name.) Ein. HSSFWorkbook wb = new HSSFWorkbook(file). workBook. } catch(Exception e) { e. } } for(short i = 0. out. // Spaltennamen Listing 133: Excel.java – Klasse zum Lesen und Schreiben von Excel-Dateien (Forts.close(). } // in Datei schreiben FileOutputStream out = new FileOutputStream(fileName). i < num.setColumnWidth(i.getSheetAt(i).printStackTrace(). i++) { // jede Spalte breit genug machen // Grundeinheit für Breite: 1/256 eines Zeichens sheet.getFirstRowNum(). int num = wb. i++) { HSSFSheet sheet = wb. for(int i = 0. i < colNum.276 >> Excel-Dateien schreiben und lesen cell. } return result. boolean firstRowAsColumnName) { ArrayList<JTable> result = new ArrayList<JTable>().setCellValue(value). int endRow = sheet. try { POIFSFileSystem file = new POIFSFileSystem(new FileInputStream(name)). } /** * Liest eine Excel-Datei und gibt alle Arbeitsblätter als JTable zurück * * @param name Dateiname * @param firstRowAsColumnName Flag.(short) (maxWidth[i] * 256)).und Ausgabe .getLastRowNum(). // erste Zeile dient zur Ermittlung der Spaltenzahl und ggf. result = true.

) Ein.firstCell + 1). data[j][k] = value. int cellType = cell.add(table). } } Listing 133: Excel. String[][] data = new String[rowNum][cellNum]. result = null. int rowNum = (int) (endRow .getCell((short) c).getRow(startRow).getLastCellNum(). short firstCell = firstRow.getNumericCellValue()). k <= endCell. if(cellType == HSSFCell. j++) { HSSFRow row = sheet. c <= lastCell. k++) { HSSFCell cell = row.getFirstCellNum(). for(int c = firstCell. int startCell = row.getRow(j). int endCell = row. result.getCell((short) k).getLastCellNum(). short lastCell = firstRow.getStringCellValue(). if(firstRowAsColumnName) startRow++.>> Ein. else value = cell.printStackTrace(). for(int k = startCell.startRow + 1). } return result.getFirstCellNum().und Ausgabe . colNames). String[] colNames = new String[cellNum].getCellType(). else colNames[c] = "".und Ausgabe (IO) 277 HSSFRow firstRow = sheet. short cellNum = (short) (lastCell .CELL_TYPE_NUMERIC) value = String. } } JTable table = new JTable(data. for(int j = startRow. String value.java – Klasse zum Lesen und Schreiben von Excel-Dateien (Forts. c++) if(firstRowAsColumnName) colNames[c] = firstRow.valueOf(cell. } } catch(Exception e){ e. j <= endRow.getStringCellValue().

wie mit Hilfe der Klasse Excel der Inhalt einer JTable-Komponente als Excel-Datei auf die Festplatte gespeichert werden kann. Als Programmierer sieht man sich daher leicht mit der Forderung konfrontiert. dass die obigen Beispiele nur die grundlegende Vorgehensweise zeigen und auch immer nur mit dem Allzwecktyp String hantieren.xls". "Kunden"). "Peter"}. String[] colNames = {"Name".und Ausgabe Abbildung 53: Erzeugte Excel-Tabelle Beachten Sie. String[][] data = {{"Müller". }. Für komplizierte Fälle inklusive eingebetteter OLE-Objekte.\\data. "Dirk"}.writeFile(table. public class Start { public static void main(String[] args) { Excel xls = new Excel(). JTable table = new JTable(data. ob nicht auch eine Ausgabe als PDF-Datei machbar wäre. Bilder und Formeln werden Sie nicht umhin kommen. ".swing. sich detaillierter in die POI-Bibliothek einzuarbeiten.278 >> PDF-Dateien erzeugen Das Start-Programm demonstriert. // Tabelle als Excel schreiben xls. {"Louis". colNames).*. welches insbesondere in der Windows-Welt weit verbreitet ist. Ähnlich wie in Rezept 112 für das Excel-Format würde Hi nwe is . } } Listing 134: Excel-Format lesen/schreiben Ein. 112 PDF-Dateien erzeugen Ein beliebtes Format zur Verteilung von Informationen ist PDF (Portable Document Format) von der Firma Adobe."Vorname"}. import javax.

sowohl für die Anzeige auf dem Bildschirm via paint() bzw. Für jede neue Seite des PDF-Dokuments fordert man nun von PDFJob mit Hilfe von getGraphics() ein PDFGraphics-Objekt an und verwendet es zum Zeichnen.jar. d.awt. Extrahieren Sie hieraus die Datei gnujpdf. Danach wird diese Ressource wieder mit seiner dispose()-Methode freigegeben. Dies erfordert allerdings einige Kenntnis über den Aufbau von Textdokumenten. deren Einsatz wir an dieser Stelle kurz demonstrieren möchten: gnujpdf ist eine Bibliothek mit der gleichen Vorgehensweise wie die AWT/Print-API in Java. man darf Fonts und Farben zuweisen und beispielsweise mit drawString() oder drawImage() entsprechende Zeichenoperationen durchführen. itext bietet volle Kontrolle und erlaubt das explizite Erzeugen fast aller PDF-Elemente im gewünschten Format und Position.shtml oder für Linux z. von einem Servlet).Graphics abgeleitete Klasse PDFGraphics an.und Ausgabe . paintComponent() als auch zur Ausgabe in eine PDF-Datei. ergibt sich unter Umständen das Problem. http://packages. Die end()-Methode von PDFJob wird aufgerufen. Dadurch kann man ohne besonderen Mehraufwand und PDF-Kenntnisse eine (mehr oder weniger) identische Ausgabe erreichen. 9..org/unstable/x11/xvfb. die neben einer üblichen Bildschirmausgabe auch eine PDF-Variante erstellt.info/data/Unix/General_UNIX/GENERAL_XvfbforSolaris. Hier hilft dann nur die Installation eines virtuellen X-Servers weiter. wobei das PDF noch eine zusätzliche zweite Seite mit weiterem Text erhält. beispielsweise Xvfb.net/projects/gnujpdf/ das aktuelle ZIP-Archiv herunter9. Diese Datei muss fürs Kompilieren und Ausführen im CLASSPATH eingetragen sein (inklusive jar-Endung).zip 10. mit der wie mit einem üblichen Graphics-Objekt gearbeitet werden kann.B.idevelopment.PrintJob (man »druckt« gewissermaßen das PDF in eine Datei).awt. die zu erzeugende Ausgabe wird mittels eines Graphics-Objekts erstellt.jpdf die von java.B. Das heißt. A cht un g Ein. Sie entspricht in ihrer Art der Klasse java. PDF mit gnujpdf erzeugen Zunächst muss natürlich die Bibliothek in Form eines jar-Archivs besorgt werden. Das PDF wird nun in eine Datei geschrieben.10 Die Bibliothek bietet in einem Paket gnu.und Arbeitsaufwand bedeuten. 10 Wichtiger Hinweis für UNIX/Linux: Wenn gnujpdf auf einem Unix-Server eingesetzt werden soll (z. Für Solaris http://www. dass kein X-Server und somit auch kein Graphics-Kontext verfügbar ist.h. Glücklicherweise finden sich mehrere brauchbare OpenSource-Bibliotheken. Das Grundmuster zum Erzeugen einer PDF-Ausgabe sieht damit wie folgt aus: Eine Instanz der Klasse PDFJob wird angelegt. Die interessanten Dinge passieren in der Klasse PaintPanel. Hierzu laden Sie von http://sourceforge.>> Ein.und Ausgabe (IO) 279 eine Eigenimplementierung einen immensen Zeit.debian. Das folgende Beispiel zeigt die konkrete Umsetzung der obigen Schritte.6. Bei Erscheinen dieses Buchs gnujpdf-1.

und Ausgabe /** * Panel-Klasse zum Zeichnen */ class PaintPanel extends JPanel { private Image image1. s. } public static void main(String[] args) { if(args. Image img2.getImage().swing.awt.*.event.*.getImage().out. String fileName) { Listing 135: Ausgabe von PDF mit gnujpdf .300). Image image1 = icon.jpdf. System.gif"). public Start(String fileName) { setTitle("PDF Test"). image2. public class Start extends JFrame { private PaintPanel panel. wenn Fenster geschlossen wird addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.*. fileName). ImageIcon icon = new ImageIcon("duke.awt.println("Aufruf: <Dateiname>"). icon = new ImageIcon("juggler. java. javax.io.setVisible(true). private Image image2.exit(0). add(panel). } Start s = new Start(args[0]). Image image2 = icon. private FileOutputStream fos. setSize(300. } }).exit(0). panel = new PaintPanel(image1.280 >> PDF-Dateien erzeugen import import import import import gnu. java. PaintPanel(Image img1.length != 1) { System.*.gif"). } } Ein.*. // Programm beenden. java.

showDuke(pdfGraphics). 0. Dies ist praktisch. 0. BOLD. g. 40). g. g.paintComponent(g). 20.getWidth(null). Font f = new Font("SansSerif". 20). Font. Font. Listing 135: Ausgabe von PDF mit gnujpdf (Forts. g. BOLD.getGraphics(). 80).drawString("Mit GnuPDF wird analog zum Graphics-Kontext eine " + "weitgehend identische PDF-Version ". 0. g.und Ausgabe /** * Erzeugt im übergebenen Graphics-Objekt die gewünschte Ausgabe */ private void showText(Graphics g) { Font f = new Font("SansSerif".drawString("Es ist das Maskottchen von Java.drawString("Dieses Männchen heißt 'Duke'. wenn man die " + " Bildschirmausgabe als PDF haben will.". height + 10. g. int width = image1. } protected void paintComponent(Graphics g) { super. g. } catch(Exception e) { e. g. } Ein.".und Ausgabe (IO) 281 super().setFont(f). 20.dispose().".drawImage(image2. pdfGraphics.drawString("erzeugt. width + 20. 100). image2 = img2. 12). image1 = img1. } } /** * Erzeugt im übergebenen Graphics-Objekt die gewünschte Ausgabe */ private void showDuke(Graphics g) { int height = image1. // PDF-Version erzeugen.>> Ein. 12). this).getHeight(null).) . // Anzeige generieren showDuke(g).printStackTrace(). try { fos = new FileOutputStream(fileName).drawImage(image1. width + 20. this).setFont(f). Graphics pdfGraphics = pdfJob. für jede Seite einen Graphics-Objekt erzeugen PDFJob pdfJob = new PDFJob(fos).

pdfJob.end(). pdfGraphics.dispose().// noch eine zweite Seite hinzufügen pdfGraphics = pdfJob.) Abbildung 54: Bildschirmausgabe via Graphics-Objekt Abbildung 55: PDF-Datei via PDFGraphics-Objekt . System.out. showText(pdfGraphics). } } Listing 135: Ausgabe von PDF mit gnujpdf (Forts.println("PDF-Datei erzeugt!").getGraphics().

*. com.50. Listing 136: PDF-Erstellung mit Hilfe von iText 11.>> Ein.*.jar . Hierzu laden Sie das aktuelle jar-Archiv sowie die zugehörige Klassendokumentation von http://sourceforge.und Ausgabe (IO) 283 PDF mit iText erzeugen Zunächst müssen Sie sich die Bibliothek in Form eines jar-Archivs besorgen. 50.0. new FileOutputStream("PDF_Demo.rtf.html.lowagie.Color. // Kopfzeile definieren HeaderFooter header = new HeaderFooter( new Phrase("Das Java-Codebook").A4. Die iText-Bibliothek definiert eine Vielzahl von Klassen.io.lowagie.pdf.lowagie.getInstance(document. 50.setHeader(header).50 Document document = new Document(PageSize.lowagie. import java. Document. import import import import com.text. true).50. Die jar-Datei muss für das Kompilieren und Ausführen im CLASSPATH eingetragen sein (inklusive jar-Endung). false).awt.setBorder(Rectangle. com. Ein. Die zentrale Klasse ist com.text. /** * Einsatz von iText zur Erzeugung eines PDF-Dokuments */ public class Start { public static void main(String args[]) { try { // Dokument anlegen im Format DIN-A4 mit Randmaßen // Abstand links/rechts/oben/unten = 50.text. // Ausgabestream öffnen PdfWriter. net/projects/itext/ herunter11. header. der man einen FileOutputStream zur Ausgabe in eine Datei übergibt. import java. mit deren Hilfe sich fast jedes gewünschte PDF-Dokument zusammenstellen lässt.text.lowagie.oder Grafikkomponenten. Bei Erscheinen dieses Buch iText 2. 50).und Ausgabe Das folgende Beispiel zeigt die grundlegende Vorgehensweise zur Erstellung und Ausgabe einer PDF-Datei.BOTTOM). 50. Sie dient als Container zur Aufnahme der gewünschten Text. com. // Fußzeile mit zentrierter Seitennummer HeaderFooter footer = new HeaderFooter(new Phrase("Seite "). Für die Ausgabe selbst stellt die Bibliothek eine Klasse PdfWriter bereit.net. document.*.0.text.pdf")).*.*.URL. import java.

16.RED)).add(image)."). Image image = Image. 12.setFooter(footer)."). die wiederum in " + "Objekten vom Typ Phrase geordnet sein können.add(chunk2).add(p4). beispielsweise RTF oder HTML!").BLUE)).gif"). Color.und Ausgabe . // Dokument öffnen document. document. footer.toURL()). document.HELVETICA. Color. Chunk chunk2 = new Chunk("Man kann einen Absatz aus vielen Chunks " + "zusammensetzen. FontFactory.getInstance(f. Chunk chunk1 = new Chunk("Kleinste Einheit ist der Chunk " + "(\"Stück\").open(). Phrase ph = new Phrase(). Paragraph p2 = new Paragraph(ph). um andere Formate " + "zu generieren. document.add(p3).NORMAL. document. den man als String inklusive " + "Font-Information auffassen kann.newPage(). Font.getFont(FontFactory. document. // Text hinzufügen Paragraph p1 = new Paragraph( "Lieber Leser. FontFactory.BOLD. Listing 136: PDF-Erstellung mit Hilfe von iText (Forts.add(p5).ALIGN_CENTER).BOLDITALIC.add(chunk1). Font. ph. " + "Es ist auch geeignet. document.TIMES_ROMAN.TIMES_ROMAN. Paragraph p4 = new Paragraph("iText kann aber noch viel mehr. Größe und " + "Farbe festlegen. // neue Seite document.".getFont(FontFactory.setBorder(Rectangle.add(p1).getFont(FontFactory. Paragraph p6 = new Paragraph("Dieses Männchen nennt sich Duke.").284 >> PDF-Dateien erzeugen footer. FontFactory. ph. Color.setAlignment(Element. Paragraph p3 = new Paragraph("Man kann natürlich auch für einen " + "ganzen Absatz die Schriftart. File f = new File("duke.toURI().". Paragraph p5 = new Paragraph("Hier beginnt eine neue Seite. Font. 12. document.add(p2).BLACK)). Sie sehen hier ein einfaches Beispiel " + "für die Erzeugung von PDF mit Hilfe der Open Source " + "Bibliothek iText.) Ein.TOP). ".

und Ausgabe Abbildung 56: PDF-Datei mit Hilfe von iText H in we is Interessant ist auch die Möglichkeit.out. Anstelle oder auch zusätzlich kann man eine Datei im RTF-Format oder als HTML erzeugen. RtfWriter2. // schließen document.printStackTrace(). einen ServletOutputStream einzusetzen.B.getInstance(document. anstatt in einen FileOutputStream zu schreiben. } } } Listing 136: PDF-Erstellung mit Hilfe von iText (Forts.add(p6). Hierdurch kann man sehr leicht serverseitig PDF-Ausgaben erzeugen. System.html")). Hierzu dienen die Klassen RtfWriter2 bzw. new FileOutputStream("HTML_Demo. HtmlWriter html = HtmlWriter. new FileOutputStream("RTF_Demo.close(). .) Ein.pdf erzeugt!").getInstance(document.>> Ein.println("Datei PDF_Demo. HtmlWriter. } catch(Exception e) { e.und Ausgabe (IO) 285 document.rtf")). z.

.

GUI . diese zum Aufbau ihrer Programme zu verwenden (um sich Arbeit zu sparen und ein konformes Erscheinungsbild zu erreichen). Gerade Letztere erwiesen sich aber im Laufe der Zeit als ungenügend. Ein AWT-Schalter zeigt daher auf den verschiedenen Plattformen immer das für die Plattform typische Erscheinungsbild. Die Vorzüge dieses Konzepts spiegeln sich direkt in der Swing-Bibliothek wider: Es gibt weit mehr Swing-Komponenten als AWT-Komponenten (da die Sun-Programmierer ja nicht mehr darauf angewiesen sind. Das AWT umfasst Klassen für die Ereignisverarbeitung. Eingabefelder etc. bestehen GUI-Anwendungen – vom Code zum Aufbau der GUI-Oberfläche einmal abgesehen – aus einzelnen Codeblöcken.. wenn das betreffende Ereignis eintritt (mehr zur Ereignisbehandlung. Der Nachteil dieses Verfahrens ist.GUI 113 GUI-Grundgerüst GUI-Anwendungen unterscheiden sich von den Konsolenanwendungen durch zwei wesentliche Punkte: Der Informations. sie legen selbst ihre Funktionalität fest und sie zeichnen sich selbst auf den Bildschirm. dass die AWT-Klassen praktisch den kleinsten gemeinsamen Nenner aller Steuerelementsätze der verschiedenen Plattformen bilden und zudem auch noch von Fehlern in deren Implementierung betroffen sind. sie greifen nicht mehr auf die plattformeigenen Implementierungen zurück. AWT und Swing Die Java-API unterstützt die GUI-Programmierung gleich mit zwei Bibliotheken: AWT und Swing. die nach dem Programmstart der Reihe nach abgearbeitet werden. Grundlage jeder GUI.und Datenaustausch zwischen Benutzer und Programm erfolgt nicht über die Konsole. Die AWT-Steuerelemente sind nämlich als Wrapper-Klassen um die plattformspezifischen Steuerelemente implementiert. Die AWT-Steuerelemente sind so implementiert. GUI-Anwendungen sind ereignisgesteuert. die Grafikausgabe. die mit bestimmten Ereignissen verbunden sind und immer dann ausgeführt werden. d. dass die angebotenen Steuerelemente auf allen Plattformen existieren). die einen anderen Ansatz verfolgt: In Swing werden die Komponenten komplett in Java implementiert. Seit Java 1.h.und Grafikprogrammierung in Java ist das AWT (Abstract Window Toolkit). Während sich Konsolenanwendungen in der Regel aus einer Folge von Anweisungen zusammensetzen. dass sie bei Ausführung auf einem Rechner auf diese betriebssysteminternen Steuerelemente zurückgreifen.2 gibt es daher eine zweite Komponentenbibliothek namens Swing. siehe Rezept 119). Jedes Betriebssystem definiert in seinem Code einen eigenen Satz typischer Steuerelemente und ermutigt Programmierer. das Drucken und anderes sowie natürlich etliche Komponentenklassen für die verschiedenen Arten von Fenstern und Steuerelementen. sondern über programmeigene Fenster und in diese eingebettete Steuerelemente (GUI-Komponenten) wie Schaltflächen.

(Insbesondere GUI-Designer. Da lag es nahe.awt. Kein Wunder also. sind nur im AWT vorhanden.*. wenn es selbst vom Benutzer geschlossen wird.) Falls Sie rein mit dem JDK arbeiten oder sich nicht von einer Entwicklungsumgebung abhängig machen wollen. sondern Sie überlassen dies der Entwicklungsumgebung.und Swing-Komponenten nicht mischen. import javax.awt. public class Grundgeruest_v1 extends JFrame { Listing 137: Swing-Grundgerüst. Ein typisches GUI-Grundgerüst definiert eine eigene Klasse für das Hauptfenster und instanziert diese in seiner main()-Methode. import java. auf der sie ausgeführt werden. dass viele Anwender Hauptfenster und Anwendung gleichsetzen. legen Sie Ihre Grundgerüste nicht selbst an. mit denen Sie per Mausklick Komponenten in Fenster einfügen oder mit Ereignisbehandlungsmethoden verbinden können. Verwenden Sie in einem Swing-Fenster also ausschließlich andere SwingKomponenten! Ansonsten kann es zu Fehlern kommen. Vorschlag 1 Achtung . welches automatisch mit dem Start der Anwendung erscheint und das die Anwendung beendet. Die entsprechenden Abschnitte sind meist mit Kommentaren gekennzeichnet.) Swing ist kein Ersatz für das AWT. denn viele grundlegenden Klassen für die GUI.und Grafikprogrammierung. insbesondere durch Verdeckung von Komponenten. dass diese Abschnitte nicht manuell bearbeitet werden sollten. insbesondere die Klassen für die Ereignisverarbeitung. (Damit die Swing-Komponenten sich wie die AWT-Komponenten der jeweiligen Plattform anpassen. musste man sie so implementieren.swing. neben den plattformtypischen Designs noch weitere Designs anzubieten und dem Programmierer die Wahl zu lassen. In den weiteren Rezepten kommen daher nahezu ausschließlich die SwingKomponenten zum Einsatz. ob er das Design der Plattform anpassen oder ein bestimmtes Design auf allen Plattformen verwenden möchte. GUI Obwohl es grundsätzlich möglich ist.*. erhalten Sie hier einige einfache Vorschläge. GUI-Grundgerüste Die meisten GUI-Anwendungen verfügen über ein Hauptfenster. dass das Grundgerüst einem bestimmten formalen Aufbau genügt. es stellt auch sicher. dass das Grundgerüst so aufgebaut wird. Wenn Sie mit einer Integrierten Entwicklungsumgebung wie dem JBuilder oder Eclipse arbeiten. Swing ist eine Komponentenbibliothek und als solche den AWT-Komponenten weit überlegen. sind darauf angewiesen. sollten Sie AWT.event. dass es von der Entwicklungsumgebung weiterbearbeitet werden kann. die den Programmierer darauf hinweisen.288 >> GUI-Grundgerüst Der Programmierer kann zwischen verschiedenen Erscheinungsbildern für seine Steuerelemente wählen. wie Sie Ihre Grundgerüste aufbauen könnten: Gemeinsame Klasse für Anwendung und Hauptfenster 01 02 03 04 05 06 import java. Dies spart nicht nur Zeit und Tipparbeit.*. siehe Rezept 147. dass sie in verschiedenen Designs gezeichnet werden können.

importieren die meisten Programmierer daher die folgenden Pakete: Paket java.event.und Swing-Klassen benötigt.setSize(500. Image etc.) Abbildung 57: Hauptfenster des GUI-Grundgerüsts Für die GUI-Programmierung werden meist eine ganze Reihe von AWT.setBackground(Color. frame.awt. Um sich Tipparbeit zu sparen und die Lesbarkeit des Quelltextes zu verbessern.300). frame. // Hier Komponenten erzeugen und mit getContentPane(). AWT-Ereignisse java.LIGHT_GRAY). Cursor. frame.300).* Tabelle 29: Wichtige GUI-Pakete GUI . } Listing 137: Swing-Grundgerüst.add() // in das Fenster einfügen setDefaultCloseOperation(JFrame.setLocation(300. Font. Color. getContentPane(). } public static void main(String args[]) { Grundgeruest_v1 frame = new Grundgeruest_v1().* Klassen für AWT-Komponenten Layout-Manager Grafikklassen wie Graphics.awt.setVisible(true).EXIT_ON_CLOSE). Vorschlag 1 (Forts.>> GUI 289 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 } public Grundgeruest_v1() { // Hauptfenster konfigurieren setTitle("Swing-Grundgerüst").

bei denen der Konstruktor ein fertiges Fenster erzeugt. H in we is Die Platzierung des Fensters auf dem Desktop unterliegt der Verantwortung des Window Managers. möchte er in der Regel auch das Programm beenden. Weit verbreitet sind Aufteilungen. für das Sie eine eigene Klasse definieren. Wenn aber der Benutzer das Hauptfenster der Anwendung schließt. Verbirgt das Fenster und löst es dann auf. Sie können sich dazu von getContentPane() eine Referenz auf die ContentPane zurückliefern lassen und deren add()-Methode aufrufen oder – ab JDK-Version 1. die das Verhalten beim Schließen eines Fensters steuern Komponenten oder untergeordnete Container werden in die ContentPane des Fensters (standardmäßig eine JPanel-Instanz) eingefügt. Zu diesem Zweck übergibt man der Methode setDefaultCloseOperation() die Konstante JFrame. Bei diesem Standardverhalten muss das Ereignis windowClosing abgefangen werden.swing. Standardmäßig bleibt das Fenster beim Drücken der Schließen-Schaltfläche aus der Titelleiste bestehen und wird lediglich unsichtbar gemacht.* Klassen für Swing-Komponenten.* javax.) GUI Die Klasse für das Hauptfenster wird von JFrame abgeleitet (Zeile 5).event.290 >> GUI-Grundgerüst Paket javax. das gerade erzeugte Objekt (Zeilen 20 bis 23). wenn es der Benutzer schließt. plus zusätzlicher Layout-Manager spezielle Swing-Ereignisse Tabelle 29: Wichtige GUI-Pakete (Forts. Dieses Verhalten ist für die untergeordneten Fenster einer Anwendung mit mehreren Fenstern durchaus sinnvoll.5 – alternativ die add()-Methode von JFrame verwenden.EXIT_ON_CLOSE. Alternative Basisklassen sind JDialog (für Dialogfenster) und JWindow (für Fenster ohne Rahmen und Titelleiste). Damit werden alle von diesem Fenster belegten Ressourcen freigegeben. bleibt weitgehend Ihnen überlassen. Konfiguriert wird das Fenster über seinen Konstruktor (Zeilen 7 bis 17) bzw. Konstante DO_NOTHING_ON_CLOSE HIDE_ON_CLOSE DISPOSE_ON_CLOSE EXIT_ON_CLOSE Beschreibung Führt keinerlei Aktionen beim Schließen des Fensters aus. Dieser braucht der »Empfehlung« des Programmcodes nicht zu folgen. Tabelle 30: Fensterkonstanten (WindowConstants). platziert (Zeilen 22 und 23) und sichtbar gemacht wird (Zeile 23). Getrennte Klassen für Anwendung und Hauptfenster Wenn Sie zwischen anwendungs.und hauptfensterspezifischem Code unterscheiden möchten. Im Konstruktor der Anwendungsklasse erzeugen Sie das Hauptfenster. Verbirgt das Fenster. Die Klasse der Anwendung enthält neben dem anwendungsspezifischen Code die main()-Methode. Welche Eigenschaft Sie wo einstellen. definieren Sie für beide eigene Klassen. Beendet die Anwendung mit System.exit(0). in der Sie das Anwendungsobjekt erzeugen. .swing. das dann über das Fenster-Objekt dimensioniert.

awt. import javax.*. } } Listing 139: Code des Hauptfensters GUI Eclipse-konformes Grundgerüst Wenn Sie Ihr Grundgerüst so aufsetzen möchten. import java.*.awt. } public static void main(String[] args) { new Grundgeruest_v2().*.setBackground(Color.setSize(500. getContentPane().setLocation(300. Des Weiteren müssen Sie die Referenz auf die ContentPane in einem private-Feld jContentPane speichern.300).*.swing. public class Grundgeruest_v2_Frame extends JFrame { public Grundgeruest_v2_Frame() { // Hauptfenster konfigurieren setTitle("Swing-Grundgerüst"). import java. frame.awt. dass die Klasse des Hauptfensters eine initialize()Methode definiert.setVisible(true).>> GUI 291 public class Grundgeruest_v2 { public Grundgeruest_v2() { Grundgeruest_v2_Frame frame = new Grundgeruest_v2_Frame().*.event. frame.*. müssen Sie darauf achten.awt.swing. // Hier Komponenten erzeugen und mit getContentPane(). } } Listing 138: Code der Anwendungsklasse import java.300). Listing 140: Eclipse-konformes GUI-Grundgerüst . frame.EXIT_ON_CLOSE). import javax.add() // in das Fenster einfügen setDefaultCloseOperation(JFrame. Die ContentPane selbst wird von einer Methode getContentPane() eingerichtet.LIGHT_GRAY).event. die vom Konstruktor aufgerufen wird. import java. dass Sie es später mit Eclipse weiterbearbeiten können.

this.LIGHT_GRAY).300).frame.setLocation(300.halbe Fensterhöhe int xPos = (dim. wird das Fenster auf dem Bildschirm zentriert. Einfacher geht es jedoch mit der Window-Methode setLocationRelativeTo(). frame. // xPos = halbe Bildschirmbreite .setSize(500. die das Fenster über einer von Ihnen spezifizierten Komponente zentriert.BorderLayout()).swing.width .swing.setVisible(true).) GUI 114 Fenster (und Dialoge) zentrieren Um ein Fenster auf dem Bildschirm zu zentrieren.setTitle("Swing-Grundgerüst"). } private javax.292 >> Fenster (und Dialoge) zentrieren public class Grundgeruest_v3 extends JFrame { private javax. initialize().frame. frame.awt. } public Grundgeruest_v3() { super().300). .getScreenSize() ein Dimension-Objekt mit den Bildschirmmaßen zurückliefern lassen und aus diesen die Koordinaten für die linke obere Ecke Ihres Fensters berechnen: // Zentrierung in eigener Regie Dimension dim = Toolkit.setLocation(xPos .setDefaultCloseOperation(JFrame. this.getScreenSize().getWidth())/2. können Sie natürlich so vorgehen.swing. frame.getDefaultToolkit(). jContentPane. dass Sie sich von Toolkit.yPos). int yPos = (dim.setContentPane(getJContentPane()). jContentPane. Wenn Sie statt einer Komponentenreferenz null übergeben.setBackground(Color.setLayout(new java. } } Listing 140: Eclipse-konformes GUI-Grundgerüst (Forts. frame. public static void main(String[] args) { Grundgeruest_v3 frame = new Grundgeruest_v3(). } private void initialize() { this.EXIT_ON_CLOSE).getHeight())/2.getDefaultToolkit().JPanel jContentPane = null.height .halbe Fensterbreite // yPos = halbe Bildschirmhöhe . } return jContentPane.JPanel().JPanel getJContentPane() { if(jContentPane == null) { jContentPane = new javax.

swing. Eine elegante Lösung für dieses Problem sieht vor. ist die Referenz auf das übergeordnete Fenster.>> GUI public static void main(String args[]) { Start frame = new Start(). } } A cht un g Vor der Zentrierung muss die Fenstergröße feststehen. befinden Sie sich im Code einer Ereignisbehandlungsmethode. Dadurch berechtigen Sie die Ereignisbehandlungsklasse. import java. die Ereignisbehandlungsklasse als innere oder anonyme Klasse innerhalb der JFrame-Klasse zu definieren.. public Start() { f = this. können Sie mit setLocationRelativeTo() auch Dialoge über ihren übergeordneten Fenstern zentrieren..awt.event.awt. "Dialog"). // Referenz auf sich selbst // Referenz initialisieren Listing 141: Zentrierung mit setLocationRelativeTo() GUI . sondern in einer eigenen Listener. auf alle Felder der übergeordneten JFrame-Klasse zuzugreifen und können in dieser ein Feld mit einer Referenz auf sich selbst ablegen: import java. frame. // FEHLER! this verweist auf // ButtonAction-Objekt d. Da diese in der Regel nicht als Methode der JFrame-Klassen. private final class ButtonAction { public void actionPerformed(ActionEvent e) { DemoDialog d = new DemoDialog(null.setVisible(true). d.setVisible(true). } 293 Dialoge zentrieren Wie im Titel dieses Rezepts versprochen. } public class Start extends JFrame { private JFrame f.setLocationRelativeTo(null).oder Adapter-Klasse definiert ist.*. class DemoDialog extends JDialog implements ActionListener { . Sofern Sie nämlich den Dialog als Antwort auf das Drücken eines Schalters oder die Auswahl eines Menübefehls erzeugen und anzeigen. dass die Fenstergröße im Konstruktor des Fensters vorgegeben wird (beispielsweise durch Aufruf von setSize().*.*. können Sie nicht mit this auf das Fenster zugreifen. Das einzige Problem. frame. import javax.setLocationRelativeTo(this). In obigem Beispiel wird davon ausgegangen. siehe Rezept 115). das sich dabei unter Umständen ergibt.

null). frame. setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE). 150.setVisible(true).add(btn.300). frame.setLocationRelativeTo(f). } } Listing 141: Zentrierung mit setLocationRelativeTo() (Forts. } GUI public static void main(String args[]) { Start frame = new Start().setVisible(true). der über seinem Fenster zentriert ist . setSize(500. 25)).setBounds(new Rectangle(300.) Abbildung 58: Dialog. "Dialog"). getContentPane().setLocationRelativeTo(null).setLayout(null). getContentPane(). btn. 200. } }). // Dialog zentrieren d.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { // Dialog erzeugen DemoDialog d = new DemoDialog(f.294 >> Fenster (und Dialoge) zentrieren // Hauptfenster einrichten setTitle("Fenster zentrieren"). // Dialog anzeigen d. btn. JButton btn = new JButton("Dialog öffnen").

Die Positionierung oder Verschiebung des Dialogs auf dem Desktop erfolgt aber gänzlich unabhängig vom Fenster. frame. Ob Sie die Fenstergröße bereits im Konstruktor oder erst nach Instanzierung des Fensters festlegen.300). diese Anfangsgröße bereits im Konstruktor festzulegen: public class MyFrame extends JFrame { public MyFrame() { setTitle("Fenster").pack().setSize(500.300). 115 Fenstergröße festlegen (und gegebenenfalls fixieren) Es gibt zwei Möglichkeiten. frame. Benutzen Sie pack() nicht.setSize(500. empfiehlt es sich... legen Sie die Fenstergröße natürlich erst nach der Instanzierung fest: MyFrame frame1 = new MyFrame().>> GUI 295 Ob Sie dem Konstruktor eines Dialogs eine Referenz auf das übergeordnete Fenster oder null übergeben. dass Ihr Fenster auf die Minimalversion einer Titelleiste zusammenschrumpft. die Fenstergröße festzulegen: Sie können die Fenstergröße mit setSize() explizit festlegen. hat keinen Einfluss auf die Positionierung. sollten Sie dies aber erst tun. nachdem Sie alle Komponenten in die ContentPane des Fensters eingefügt haben. ist Ihre freie Entscheidung. frame2. MyFrame frame2 = new MyFrame(). Wenn Sie eine Fensterreferenz übergeben. GUI . Wenn Sie die Fenstergröße von pack() berechnen lassen wollen. setSize(500. Sie wünschen. H in we is Sie können die Fenstergröße durch Aufruf von pack() an den Inhalt des Fensters anpassen. Wenn alle Instanzen der Fensterklasse anfangs die gleiche Größe haben sollen. Achtung Fenster fester Größe Wenn Sie die Fenstergröße fixieren möchten. wenn Sie für das Fenster keinen Layout-Manager verwenden (frame.setLayout(null)) oder es keine dimensionierten Komponenten enthält – es sei denn. wird das Fenster zum Besitzer (owner) des Dialogs und dieser wird beispielsweise mit dem Fenster minimiert oder wiederhergestellt. so dass sie nicht vom Anwender verändert werden kann.300). . frame1. rufen Sie die Methode setResizable() mit false als Argument auf: A c htu n g Die Methode setSize() können Sie bereits eingangs des Konstruktors aufrufen.setSize(300. Wenn Sie von einer Fensterklasse mehrere Instanzen unterschiedlicher Maße (Abmessungen) erzeugen möchten.300).

. die darüber informieren. In Java gehen alle Fensterklassen auf die Basisklasse Component zurück.*. import javax. setDefaultCloseOperation(JFrame.*. verschoben oder neu dimensioniert wird.swing.swing. setSize(500. private final int MINHEIGHT = 100. import javax.awt. public class Start extends JFrame implements ComponentListener { private final int MINWIDTH = 300.EXIT_ON_CLOSE). public Start() { setTitle("Fenster mit Minimalgröße"). diesem Missstand zu begegnen. frame.setVisible(true). Eine von zweifelsohne mehreren Möglichkeiten1.*. MINHEIGHT). import java. Sie lösen also Ereignisse vom Typ ComponentEvent aus. Letzteres Ereignis.setLocation(300.300). } } Listing 142: Fenster fester Größe GUI 116 Minimale Fenstergröße sicherstellen Sie kennen das: Mit viel Liebe und Ausdauer haben Sie das optimale Layout für die Komponenten Ihres Fensters ausgearbeitet und implementiert und dann gehen die Anwender hin und verkleinern das Fenster.*. setSize(MINWIDTH. bis von Ihrem Layout nichts mehr übrig bleibt. verborgen. } public static void main(String args[]) { Start frame = new Start(). zu welchem die Ereignisbehandlungsmethode componentResized() gehört. Listing 143: Auf Größenänderungen reagieren 1.awt.296 >> Minimale Fenstergröße sicherstellen import java. setResizable(false). ist die Vorgabe einer Minimalgröße. unter die das Fenster nicht verkleinert werden kann.300). auf Größenänderung zu reagieren – beispielsweise um eine Mindestgröße sicherzustellen. Siehe Rezept 115 zur Fixierung der Fenstergröße.*.awt. public class Start extends JFrame { public Start() { setTitle("Fenster fester Größe"). gibt uns die Möglichkeit. frame.event. import java. wenn eine Komponente angezeigt.

add(btn). wird der Mindestwert eingesetzt und das Fenster mit einem Aufruf von setSize() neu dimensioniert. } public static void main(String args[]) { Start frame = new Start().height < MINHEIGHT) ? MINHEIGHT: dim.) Nachdem sich die Fensterklasse bei sich selbst als Empfänger für Component-Ereignisse registriert hat (addComponentListener(this) im Konstruktor). addComponentListener(this). eine eigene Klasse von ComponentAdapter abzuleiten. frame. setDefaultCloseOperation(JFrame. btn. weswegen wir verpflichtet sind. 150.width = (dim. } }). wird die Methode componentResized() automatisch aufgerufen. dim. } public } public } public } public GUI void componentHidden(ComponentEvent e) { void componentMoved(ComponentEvent e) { void componentShown(ComponentEvent e) { void componentResized(ComponentEvent e) { // Gegebenenfalls minimale Fenstergröße herstellen Dimension dim = this.width < MINWIDTH) ? MINWIDTH: dim.setText("Danke"). Die Methode fragt dann die neue Größe ab und prüft.setSize(dim). btn.setLocationRelativeTo(null). in dieser componentResized() zu überschreiben und dann ein Objekt dieser Klasse als Listener zu registrieren. getContentPane().) In obigem Beispiel implementiert die Klasse Start das Interface ComponentListener höchstpersönlich. .width .addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { ((JButton) e.getSource()).height = (dim.>> GUI 297 JButton btn = new JButton("Klick mich"). (Die Alternative wäre. } } Listing 143: Auf Größenänderungen reagieren (Forts.EXIT_ON_CLOSE). wenn sich die Größe des Fensters ändert. dim.getSize().setVisible(true). Wenn ja. this. ob Breite oder Höhe unter den in MINWIDTH und MINHEIGHT gespeicherten Grenzwerten liegen. in der Klassendefinition für alle Methoden des Interface Definitionen bereitzustellen. 20. 25)).height . frame.setBounds(new Rectangle(75.

getHeight(this). java. this. javax. // // // // Linke. this).event. public class Start extends JFrame { private Image bgImage = null.drawImage(bgImage. 3. // innere Klasse für ContentPane private class ContentPane extends JPanel { public ContentPane() { setLayout(new FlowLayout()). Diesen können Sie mittels der Methode setBackground() beliebig einfärben: getContentPane().*. java. 2. java. müssen Sie hingegen schon etwas mehr Aufwand treiben. import import import import import import import java.drawImage(bgImage. ist der Hintergrund der ContentPane. damit Sie deren paintComponent()-Methode überschreiben und mittels drawImage() das Bild einzeichnen können.Image.IOException. javax. 0.setBackground(Color.*.298 >> Bilder als Fensterhintergrund 117 Bilder als Fensterhintergrund Was der Anwender als Hintergrund eines Fensters (oder Dialogs) wahrnimmt.awt.File. obere Ecke sowie Breite und Höhe des Zielbereichs. in den das Bild eingepasst wird oder die eigenen Originalmaße beibehält: g.*. Schließlich erzeugen Sie eine Instanz Ihrer ContentPane-Klasse und richten diese als ContentPane des Fensters ein. 0. bgImage.imageio.getWidth(this). // weißer Hintergrund Wenn Sie als Hintergrund ein Bild anzeigen möchten. bgImage. GUI Mit drawImage() können Sie das Bild wahlweise so einzeichnen. 0. 0. Sie müssen das Bild laden.ImageIO.awt.getHeight(). dass es an die Maße der ContentPane angepasst ist: g. } Listing 144: Bild als Hintergrund der ContentPane // 2 . java.awt.swing. this. 1.WHITE). Sie müssen für die ContentPane eine eigene Klasse von JPanel ableiten.io.getWidth().io. this).

drawImage(bgImage.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.>> GUI 299 public void paintComponent(Graphics g) { super.setVisible(true). this). getContentPane().add(btn). btn. Color.WHITE.paintComponent(g). // Hintergrundbild in Panel zeichnen if (bgImage != null) g. setDefaultCloseOperation(JFrame. frame. } } public Start() { setTitle("Fenster mit Hintergrundbild").) // 3 GUI // 1 . // Bilddatei laden try { bgImage = ImageIO. this. frame.getWidth(). 0.jpg")).EXIT_ON_CLOSE). 0. JButton btn = new JButton("Beenden"). this.300).setLocation(300. frame. } } Listing 144: Bild als Hintergrund der ContentPane (Forts. } catch(IOException ignore) { } setContentPane(new ContentPane()).getHeight(). } }).exit(0). } public static void main(String args[]) { Start frame = new Start().setSize(500.300).read(new File("background.

) 2. Für Fenster variabler Größe eignen sich am besten skalierte Strukturbilder oder Motivbilder. weil er von Hand oder mit einem anderen. 118 Komponenten zur Laufzeit instanzieren Die Instanzierung von Komponenten ist eine Aufgabe. wird das Hintergrundbild automatisch durch Skalierung an die neue Fenstergröße angepasst. Erzeugen Sie ein Objekt der Komponente. wenn eine solche Referenz nicht benötigt wird. dass Komponenten nur nach Bedarf instanziert werden sollen. dass der Programmierer selbst Hand anlegen muss: sei es. nichtkompatiblen GUI-Designer erstellt wurde.oder JButton-Komponenten oder die Optionen eines JList-Felds. Motivbilder. Kann der Anwender die Fenstergröße verändern. Tipp . dass das Motiv entweder stark verzerrt wird (bei Skalierung des Bilds) oder nur als Ausschnitt (Fenster ist kleiner als Bild) bzw. Deklarieren Sie in der Fensterklasse ein Feld vom Typ der Komponente. oberen Bereich beschränkt ist. führt dies bei Motivbildern meist dazu. Eingabefeldern etc. um später jederzeit von beliebiger Stelle aus über dieses Feld auf die Komponente zugreifen zu können. Meist können Sie dem Konstruktor dabei bereits Argumente zur Konfiguration der Komponente übergeben: zum Beispiel den Titel für JLabel. Für die Bestückung eines Fensters oder Dialogs mit Schaltern. dass der Schalter über dem Hintergrundbild der ContentPane liegt. (Kann entfallen. Wenn Sie die Fenstergröße verändern. wie oben zu sehen. Hier eine kleine Checkliste. als Teil des Fensters (Fenster ist größer als Bild) zu sehen ist. welche Schritte bei der manuellen Instanzierung von Komponenten zu beachten sind: 1. gibt es kaum etwas Besseres.300 >> Komponenten zur Laufzeit instanzieren GUI Abbildung 59: Beachten Sie. sollten Sie anders als im Demobeispiel nur für Fenster fixer Größe (siehe Rezept 115) als Hintergrund verwenden. Textlabeln. die man üblicherweise gerne dem GUIDesigner einer leistungsfähigen Entwicklungsumgebung überlässt (beispielsweise JBuilder oder Eclipse). der sich nicht in den GUI-Designer einlesen lässt. dass Code überarbeitet werden muss. sei es. bei denen das Motiv auf den linken. Trotzdem kommt es immer wieder vor. Listenfelder.

btn. Legen Sie Größe und Position der Komponente fest. Dies kann explizit geschehen (Methoden setBounds(). (Arbeitet der Container. // kein Layout-Manager btn = new JButton("Klick mich").*. können Sie mittels setMaximumSize(Dimension). Fügen Sie die Komponente in ein Fenster (oder eine untergeordnete Container-Komponente) ein.*.awt. } }). Listing 145: Einfügen eines JButton-Schalters in ein Fenster // 2 // 3 // 4 // 1 . Hinter.setBounds(new Rectangle(75. soweit dies nicht bereits vom Konstruktor erledigt wurde. mit einem LayoutManager. 5.*. import java. 120. setSize(). public class Start extends JFrame { JButton btn. Um eine Komponente in ein Fenster oder irgendeinen anderen Container (beispielsweise eine JPanel-Instanz) einzufügen.und Vordergrundfarbe (setBackground(Color). 150. import java. sich von der Fenstermethode getContentPane() eine Referenz auf die ContentPane des Fensters zurückliefern zu lassen und dann deren add()-Methode aufzurufen.awt. setResizable(false). Komponenten in Swing-Fenster einzufügen. setForeground(Color)). import javax.200). 25)). setLayout(null).swing. wurden die Swing-Fenster ab JDK 1.event. in den Sie die Komponente einfügen. Der korrekte Weg. Um das Einfügen von Komponenten zu vereinheitlichen. Behandeln Sie gegebenenfalls Ereignisse der Komponente. setMinimumSize(Dimension) und setPreferredSize(Dimension) Hinweise für die Dimensionierung der Komponente geben. Konfigurieren Sie die Komponente. Swing-Fenster betten Komponenten in ihre ContentPane (standardmäßig eine JPanelInstanz) ein. ist demnach.>> GUI 301 3. Erscheinungsbild des Cursors über der Komponente (setCursor(Cursor)) und Aktivierung (setEnabled(boolean)) fest. rufen Sie die add()-Methode des Containers auf. H inwe is GUI 4.getSource()).setText("Danke"). Konfigurieren Sie die komponentenspezifischen Eigenschaften. setPosition()) oder vom Layout-Manager übernommen werden. public Start() { setTitle("Komponenten einfügen").5 mit einer add()-Methode ausgestattet.) Legen Sie nach Wunsch Schriftart (setFont(Font)). btn. setSize(300.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { ((JButton) e. die die übergebene Komponente automatisch in die ContentPane einfügt.

ist bereits auf dem Bildschirm sichtbar.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { // Beim ersten Drücken des Schalters ein Label-Feld erzeugen if (lb == null) { lb = new JLabel(""). Dazu rufen Sie die repaint()-Methode des Fensters auf: public class Start_dynamisch extends JFrame { JButton btn. 120. btn.addMouseListener(new MouseListener() { public void mouseClicked(MouseEvent e) {} public void mouseEntered(MouseEvent e) {} Listing 146: Komponente nach Bedarf instanzieren . } public static void main(String args[]) { Start frame = new Start(). JFrame f. Sie müssen auch explizit dafür Sorge tragen. in das die Komponente eingefügt wird. public Start_dynamisch() { .. setDefaultCloseOperation(JFrame. 50.setLocation(300. Wenn das Programm später ausgeführt wird und das Fenster zum ersten Mal auf dem Bildschirm erscheint.getWidth()-40. Es genügt daher nicht. btn = new JButton("Klick mich"). } } Listing 145: Einfügen eines JButton-Schalters in ein Fenster (Forts. Font.. f = this. also zum Beispiel als Reaktion auf eine Benutzeraktion.EXIT_ON_CLOSE). btn.) GUI // 5 Komponenten dynamisch zur Laufzeit instanzieren Im vorangehenden Beispiel wurde die Komponente manuell im Konstruktor erzeugt und in das Fenster eingefügt. lb. lb. die Komponente einfach nur in das Fenster einzufügen. setLayout(null). Wenn Sie eine Komponente dynamisch. 25)). sind die Voraussetzungen dagegen meist andere: Das Fenster. f.300).setBounds(new Rectangle(20. wird die eingebettete Komponente automatisch als Bestandteil des Fensters mit auf den Bildschirm gezeichnet. 20)). frame. erzeugen.PLAIN. 25)).add(btn).setVisible(true). 150.setBounds(new Rectangle(75.302 >> Komponenten zur Laufzeit instanzieren getContentPane(). lb. JLabel lb. dass das Fenster neu gezeichnet wird.setFont(new Font("Arial". frame.

Jedes ListenerInterface definiert. 119 Komponenten und Ereignisbehandlung In Java gibt es viele verschiedene Möglichkeiten. eine Ereignisbehandlung aufzubauen. Um Ereignisquelle (die Komponente) und Ereignisempfänger (das Lauscher-Objekt) zu koordinieren. welches ein bestimmtes Ereignis empfangen und verarbeiten möchte. Ein Lauscher-Objekt. nicht beim Start des Programms. manchmal ist es auch eine reine Design-Entscheidung.getText() + "Danke "). Einen Königsweg gibt es nicht. wie die Ereignisbehandlungsmethoden für eine bestimmte Gruppe von Ereignissen (manchmal auch nur ein einziges Ereignis) heißen. definiert die Java-API eine Reihe von so genannten Listener-Interfaces. getContentPane(). d. Manchmal geben die äußeren Umstände den richtigen Weg vor. beim ersten Drücken des Schalters. muss vom Typ einer Klasse sein.getSource()).add(lb). wenn es wirklich gebraucht wird. Dieses Rezept stellt Ihnen – nach einer kurzen Rekapitulation des Grundmechanismus der Ereignisbehandlung in Java – einige weit verbreitete Grundtypen vor.EXIT_ON_CLOSE). welches von dem Schalter als Ausgabefeld benutzt wird. Mechanismus der Ereignisbehandlung in Java Ereignisse auf Komponenten (inklusive Fenstern) werden in Java dadurch behandelt.setText("").getContentPane(). Wird der Schalter während der Ausführung des Programms überhaupt nicht gedrückt (was in diesem Beispiel zugegebenermaßen unwahrscheinlich ist).) Dieses Beispiel instanziert das JLabel-Feld lb.>> GUI 303 public void mouseExited(MouseEvent e) {} public void mousePressed(MouseEvent e) {} public void mouseReleased(MouseEvent e) { ((JLabel) e.add(btn). f. ruft die Komponente die zugehörige Ereignisbehandlungsmethode des registrierten Lauscher-Objekts auf. sondern erst. } Listing 146: Komponente nach Bedarf instanzieren (Forts.h. der als Antwort auf .repaint(). damit neue Komponente // angezeigt wird f. Tritt das Ereignis ein. GUI setDefaultCloseOperation(JFrame.setText(lb. } lb. } }). werden die Kosten für die Instanzierung eingespart. // Fenster neu zeichnen lassen. } }). die das zugehörige Listener-Interface implementiert und dabei die Behandlungsmethode für das Ereignis mit dem Code definiert. dass bei der betreffenden Komponente ein Lauscher-Objekt mit einer passenden Ereignisbehandlungsmethode registriert wird.

welche Typen von Ereignisempfängern bei ihnen registriert werden. Das Java-Modell der Ereignisbehandlung Die Ereignisbehandlung von Java beruht auf Ereignisquellen und Ereignisempfängern. die als Argument ein Objekt vom Typ eines Listener-Interface erwarten: addActionListener(ActionListener l) addComponentListener(ComponentListener l) . Die API stellt allerdings nicht für alle Interfaces mit mehreren Methoden passende AdapterKlassen zur Verfügung. die Komponente möchte dem Programmierer die Gelegenheit geben. H in we is E x ku r s Im Anhang zur Java-Syntax finden Sie tabellarische Auflistungen der wichtigsten Interfaces mit ihren Ereignisbehandlungsmethoden sowie den zugehörigen AdapterKlassen und Ereignisobjekten. . sprich die Komponenten. registrieren Ereignisquelle senden Ereignis empfangen Ereignisempfänger Abbildung 60: Java-Ereignisbehandlungsmodell Ausgangspunkt ist. dass sie bei Eintritt eines Ereignisses die zugehörigen Ereignisbehandlungsmethoden aller registrierten Ereignisempfänger ausführen.304 >> Komponenten und Ereignisbehandlung das Ereignis ausgeführt werden soll. Dazu tritt sie selbst als Ereignisquelle auf und sendet allen interessierten Ereignisempfängern ein Ereignisobjekt. an deren Bearbeitung der Programmierer nicht interessiert ist. dass ein bestimmtes Ereignis in einer Komponente auftritt. Ein solches Ereignis kann die vom Betriebssystem vermittelte Benachrichtigung über eine Benutzeraktion auf der Komponente sein (beispielsweise das Anklicken der Komponente). GUI Indem die Komponentenklassen selbst festlegen. und überschreibt lediglich die ihn interessierenden Methoden. das über das eigentliche Ereignis informiert. auf dieses Ereignis zu reagieren. in denen die Ereignisse auftreten. können sie bestimmen. es kann sich aber auch um eine Zustandsänderung der Komponente handeln (der Wert eines Felds wurde geändert). Die Registrierung erfolgt über spezielle Registrierungsmethoden.. welche Registrierungsmethoden sie anbieten. Sind in dem Listener-Interface noch weitere Ereignisbehandlungsmethoden deklariert.. Diese sind so implementiert. sprich welche Ereignisse für die Komponente behandelt werden können. Wie auch immer. die statt seiner die Ereignisbehandlungsmethoden des Interface mit Leerdefinitionen implementiert. (Oder er leitet seine Lauscher-Klasse von einer Adapter-Klasse ab.) Auf der anderen Seite des Ereignismodells stehen die Ereignisquellen. kann er diese mit leerem Anweisungsteil definieren.

import java.300). dass sich die Ereignisempfänger bei der Ereignisquelle registrieren (die passenden Registrierungsmethoden definiert die Klasse der Ereignisquelle). public class Start_Container extends JFrame implements ActionListener { JButton btn. Voraussetzung ist natürlich. Ereignisbehandlung durch Container Komponenten werden in Container eingebettet. dass Sie die Container-Klasse selbst definieren. 25)).setBounds(new Rectangle(0.CENTER.*. 0. indem man die ContainerKlasse die betreffenden Listener-Interfaces implementieren lässt. Tritt dann ein Ereignis auf. p.*. import javax. p. 150. setDefaultCloseOperation(JFrame. public Start_Container() { setTitle("Ereignisbehandlung"). in dem spezielle Methoden zur Behandlung des Ereignisses definiert sind. ruft die Ereignisquelle für alle bei ihr registrierten Ereignisempfänger die passende Ereignisbehandlungsmethode auf – mit dem Ereignisobjekt als Argument. // Schalter erzeugen btn = new JButton("Klick mich"). Da liegt es nahe.EXIT_ON_CLOSE).setEnabled(false).add(p. getContentPane(). setSize(500. dem Container auch gleich die Verantwortung für die Ereignisbehandlung zu übertragen. // Fenster als Ereignisempfänger bei Schalter registrieren btn. JPanel p = new JPanel().setLayout(new FlowLayout(FlowLayout.add(btn).awt.SOUTH). BorderLayout.awt.event. Tatsächlich ist es so. Unter dem »Senden« des Ereignisobjekts ist also der Aufruf der registrierten Methode mit dem Ereignisobjekt als Argument zu verstehen. btn. } Listing 147: Container als Ereignisempfänger (aus Start_Container. import java.swing.20)).>> GUI 305 Der Begriff »senden« stammt aus der traditionellen objektorientierten Terminologie und sollte nicht zu wörtlich genommen werden.*.java) GUI . } // Ereignisbehandlung für Schalter public void actionPerformed(ActionEvent e) { btn.addActionListener(this).10. Zudem muss der Ereignisempfänger ein Interface implementieren.

) Ereignisbehandlung mit inneren Klassen Sie können innere Klassen zur Ereignisbehandlung definieren. public Start_Innere() { setTitle("Ereignisbehandlung").event. setSize(500.add(p.awt.300). import javax.*. 25)). import java. 0. getContentPane().setLocationRelativeTo(null). JPanel p = new JPanel().swing.10.EXIT_ON_CLOSE).awt.add(btn). setDefaultCloseOperation(JFrame. public class Start_Innere extends JFrame { JButton btn. frame.CENTER. Listing 148: Innere Klasse als Ereignisempfänger (aus Start_Innere. // Schalter erzeugen btn = new JButton("Klick mich"). } } Listing 147: Container als Ereignisempfänger (aus Start_Container.setEnabled(false).20)).306 >> Komponenten und Ereignisbehandlung public static void main(String args[]) { Start_Container frame = new Start_Container(). p.*. BorderLayout.SOUTH). 150.setVisible(true). // ButtonListener als Ereignisempfänger für Schalter registrieren btn. frame. import java. } // Ereignisempfänger-Klasse class ButtonListener implements ActionListener { public void actionPerformed(ActionEvent e) { btn.addActionListener(new ButtonListener()). btn.java) (Forts.java) GUI . } } public static void main(String args[]) { Start_Innere frame = new Start_Innere().setLayout(new FlowLayout(FlowLayout.*.setBounds(new Rectangle(0. p.

dass Sie in den Methoden der inneren Klasse uneingeschränkt auf die Felder und Methoden der Fensterklasse zugreifen können (selbst wenn diese private sind).*. JPanel p = new JPanel().add(p.setBounds(new Rectangle(0. 25)). } Listing 149: Anonyme Klasse als Ereignisempfänger (aus Start_Anonym. setSize(500.SOUTH). // Schalter erzeugen btn = new JButton("Klick mich"). public class Start_Anonym extends JFrame { JButton btn.setVisible(true).CENTER. p. 0. Für umfangreichere Ereignisbehandlungen ist dieses Modell weniger gut geeignet. frame.300). p. public Start_Anonym() { setTitle("Ereignisbehandlung").*. } } Listing 148: Innere Klasse als Ereignisempfänger (aus Start_Innere. } }). dass der Ereignisbehandlungscode direkt bei dem Code zur Erzeugung und Konfiguration der Komponente steht.20)). BorderLayout.*. import javax.add(btn).java) Achtung . setDefaultCloseOperation(JFrame.10.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { btn. GUI Ereignisbehandlung mit anonymen Klassen Sie können anonyme Klassen zur Ereignisbehandlung definieren.awt. Die Definition als innere Klasse hat jedoch den Vorteil. Dies hat den Vorteil.swing. btn. getContentPane(). import java.java) (Forts. 150.setLocationRelativeTo(null).) Sie können die Ereignisempfänger-Klasse selbstverständlich auch als eigenständige Klasse außerhalb der Fensterklasse definieren.event.setEnabled(false). // Anonyme Klasse als Ereignisempfänger für Schalter registrieren btn.awt.EXIT_ON_CLOSE).>> GUI 307 frame.setLayout(new FlowLayout(FlowLayout. import java.

} }). public class Start_NtoN extends JFrame { JButton btn1. btn2.308 >> Komponenten und Ereignisbehandlung public static void main(String args[]) { Start_Anonym frame = new Start_Anonym(). } }).addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { btn2. mehrere Ereignisempfänger definieren (einen für jede Quelle). Listing 150: Individuelle Ereignisempfänger für das gleiche Ereignis unterschiedlicher Komponenten (aus Start_NtoN. btn1. 0.300). 25)). JButton btn2.setEnabled(true). import java.setBounds(new Rectangle(0.*. 0.java) GUI . import java.) Individuelle Ereignisbehandlungsmethoden Sie können für ein Ereignis. btn2. public Start_NtoN() { setTitle("Ereignisbehandlung"). btn2. 25)).setEnabled(true). } } Listing 149: Anonyme Klasse als Ereignisempfänger (aus Start_Anonym. das von mehreren Ereignisquellen ausgelöst wird.awt. frame.*. setSize(500. import javax.setBounds(new Rectangle(0. btn1.setEnabled(false).*. 150. btn1. 150.awt. // Schalter erzeugen btn1 = new JButton("Klick mich"). frame.setEnabled(false).addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { btn1.java) (Forts.setVisible(true).event.setLocationRelativeTo(null).swing. btn2 = new JButton("Klick mich").

SOUTH). } } Listing 150: Individuelle Ereignisempfänger für das gleiche Ereignis unterschiedlicher Komponenten (aus Start_NtoN. JButton btn2.*.event. welcher Schalter gedrückt wurde. public Start_Nto1() { setTitle("Ereignisbehandlung").setLayout(new FlowLayout(FlowLayout. p. p. frame. import java. BorderLayout.*.10. 0. p. einen gemeinsamen Ereignisempfänger definieren und gegebenenfalls in den Ereignisbehandlungsmethoden mit Hilfe der Informationen aus dem Ereignisobjekt zwischen den Ereignisquellen unterscheiden.setLocationRelativeTo(null).swing.awt. Listing 151: Ein Ereignisempfänger für das gleiche Ereignis unterschiedlicher Komponenten (aus Start_Nto1. das von mehreren Ereignisquellen ausgelöst wird. setDefaultCloseOperation(JFrame.add(p. die allen Ereignisobjekten zu eigen ist. 25)). Durch Vergleich dieser Referenz mit den Feldern für die Komponenten stellt die Methode fest. frame. // Schalter erzeugen btn1 = new JButton("Klick mich").add(btn1). import javax. eine Referenz auf die auslösende Komponente zurückliefern. public class Start_Nto1 extends JFrame { JButton btn1. 150.add(btn2).>> GUI 309 JPanel p = new JPanel().setBounds(new Rectangle(0.awt.CENTER. btn1.EXIT_ON_CLOSE).java) (Forts. } public static void main(String args[]) { Start_NtoN frame = new Start_NtoN(). import java.java) GUI .setVisible(true).) Gemeinsam genutzte Ereignisbehandlungsmethoden Sie können für ein Ereignis. setSize(500. getContentPane().*. Das folgende Beispiel lässt sich beispielsweise über die Methode getSource().300).20)).

150. frame.setEnabled(false).310 >> Aus Ereignismethoden auf Fenster und Komponenten zugreifen btn1.20)). setDefaultCloseOperation(JFrame.addActionListener(new ButtonListener()).setLayout(new FlowLayout(FlowLayout.setEnabled(true).10.setVisible(true). getContentPane().add(p. } GUI // Innere Klasse zur Ereignisbehandlung class ButtonListener implements ActionListener { public void actionPerformed(ActionEvent e) { if (e. Sie brauchen sich einfach nur von der getSource()-Methode des Ereignisobjekts eine Referenz auf die Komponente zurückliefern zu lassen.setLocationRelativeTo(null). Dabei gilt: Die aktuelle Komponente. aus den Ereignisbehandlungsmethoden der Komponenten heraus auf die aktuelle Komponente.getSource() == btn2) { btn2. p. wenn in der Fensterklasse Felder für .setEnabled(false). BorderLayout.add(btn1).SOUTH). p.CENTER. btn2.java) (Forts. btn2. btn2 = new JButton("Klick mich"). 0. btn1. Dies ist beispielsweise der Fall. } } } public static void main(String args[]) { Start_Nto1 frame = new Start_Nto1(). JPanel p = new JPanel(). p.getSource() == btn1) { btn1. } else if (e. Auf andere Komponenten kann nur zugegriffen werden.add(btn2).setBounds(new Rectangle(0. wenn Referenzen auf die Komponenten verfügbar sind.setEnabled(true).) 120 Aus Ereignismethoden auf Fenster und Komponenten zugreifen Häufig ist es notwendig.EXIT_ON_CLOSE). } } Listing 151: Ein Ereignisempfänger für das gleiche Ereignis unterschiedlicher Komponenten (aus Start_Nto1. ist immer über das Ereignisobjekt greifbar. frame. andere Komponenten des Fensters oder das Fenster selbst zuzugreifen. btn2. für die das Ereignis ausgelöst wurde. 25)).addActionListener(new ButtonListener()).

import javax.setTitle(frame.awt.swing.a. sich von der aktuellen Komponente über getParent()-Aufrufe den Weg bis zum Fenster zu bahnen.>> GUI 311 die Komponenten definiert wurden und die Klasse mit der Ereignisbehandlungsmethode eine innere oder anonyme Klasse der Fensterklasse ist. btn.*.*.awt. public class Start extends JFrame { // Felder.getTitle() + " . (Innere Klassen können ohne Einschränkung durch die Zugriffsspezifizierer auf die Elemente der äußeren Klasse zugreifen. auch den Zugriff aus inneren Klassen gestatten private JFrame frame. Grundsätzlich ist es auch möglich.java) GUI . 150. Listing 152: Zugriffstechniken für Ereignisbehandlungsmethoden (aus Start. // Schalter erzeugen und Referenz in Feld speichern btn = new JButton("Klick mich").getSource()). wenn das Fenster selbst der Ereignisempfänger ist (sprich die Ereignisbehandlungsmethode eine Methode des Fensters ist).300). // Zugriff auf Fenster über Feld frame frame. private JButton btn.) Auf das Fenster können Sie über die this-Referenz zugreifen. H i n we i s import java.setBounds(new Rectangle(0. public Start() { setTitle("Ereignisbehandlung"). setSize(500.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { // Zugriff auf aktuelle Komponente über getSource() ((JButton) e. // Referenz auf Fenster in Feld frame abspeichern frame = this. sondern auf die Instanz der inneren Klasse zu.Angeklickt").event. die u. 25)).*. Wenn die Ereignisbehandlungsmethode zu einer inneren oder anonymen Klasse des Fensters gehört. Für den Zugriff auf das Fenster muss dann in der Fensterklasse ein Feld definiert und in diesem die Referenz auf das Fenster gespeichert werden. import java. btn.setText("Angeklickt"). // Zugriff auf Komponente über Feld btn.setEnabled(false). Der zugehörige Code ist wegen der erforderlichen Castings aber recht hässlich und bereits für kleinere Container-Hierarchien schlecht lesbar und ineffizient. greift this nicht auf das Fenster-Objekt. 0.

150.setBounds(new Rectangle(0. 25)). // JPanel erzeugen JPanel p = new JPanel(). BorderLayout. Komponenten.SOUTH). Listing 153: Zentrieren mit FlowLayout – Version 1 . durch dynamische Positionsberechnung (falls die Fenstergröße verändert werden kann). Um eine einzelne Komponente (oder eine Zeile von Komponenten) mittels eines FlowLayoutManagers zu zentrieren.. die in einen Container mit FlowLayout eingefügt werden. . Wie könnte man zum Beispiel einen Schalter horizontal zentriert am unteren Rand eines Fensters anzeigen (siehe Abbildung 61)? Die ContentPane eines JFrame-Fensters ist standardmäßig eine JPanel-Instanz mit BorderLayout. 0. Sie könnten den Schalter direkt in den SOUTH-Bereich der ContentPane einfügen. GUI durch statische Positionsberechnung (falls die Fenstergröße nicht verändert werden kann). Er liegt dann am unteren Rand..312 >> Komponenten in Fenster (Panel) zentrieren } }).add(p. die für die Komponente (Komponentenzeile) einen eigenen Container vorsieht. p. füllt diesen Bereich allerdings vollständig aus.) 121 Komponenten in Fenster (Panel) zentrieren Es gibt verschiedene Möglichkeiten.java) (Forts. Zentrieren mit Layout-Managern Der FlowLayout-Manager eignet sich für die Zentrierung von Komponenten besonders gut. erzeugen Sie eine JPanel-Instanz. btn. Dann fügen Sie den Schalter in das JPanel und das JPanel in den SOUTH-Bereich der ContentPane ein. werden zeilenweise von oben nach unten angeordnet.add(btn). // Panel in SOUTH-Bereich der ContentPane einfügen getContentPane(). wenn Sie Ihre GUIOberfläche ohnehin mit Layout-Managern konstruieren). Um den Schalter zu zentrieren. // Nutzt standardmäßig FlowLayout // Schalter erzeugen und in Panel einfügen btn = new JButton("Klick mich"). wobei die Zeilen im Container standardmäßig horizontal zentriert werden. müssen Sie eine Container-Hierarchie aufbauen. Komponenten zu zentrieren: mit der Hilfe geeigneter Layout-Manager (empfiehlt sich insbesondere. } Listing 152: Zugriffstechniken für Ereignisbehandlungsmethoden (aus Start.

können Sie die Komponente leicht selbst zentrieren. Listing 155: Zentrieren eines Schalters in einem Dialogfenster ohne Layout-Manager (aus Start.java) Abbildung 61: Zentrierter Schalter Zentrieren durch statische Berechnung Hat das Fenster – oder allgemeiner der Container. Als Ergebnis erhalten Sie die x. title). // Schalter erzeugen und in Panel einfügen btn = new JButton("Klick mich "). unveränderliche Abmessungen. Dialogfenster haben meist eine feste Größe (siehe auch Rezept 115).>> GUI 313 Den Abstand des Schalters vom unteren Rahmen können Sie über den vgap-Wert des LayoutManagers einstellen.add(btn). BorderLayout. p.setLayout(new FlowLayout(FlowLayout. yKoordinate für die linke obere Ecke der Komponente. btn.SOUTH). Der folgende Code zentriert einen Schalter am unteren Rand des Dialogs: class DemoDialog extends JDialog implements ActionListener { public DemoDialog(Frame owner.add(p.setBounds(new Rectangle(0. 10.CENTER. 25)). // Panel in SOUTH-Bereich der ContentPane einfügen getContentPane().bzw. in den die Komponente eingefügt wird – feste.java) GUI . // JPanel mit individuellem Layout-Manager erzeugen JPanel p = new JPanel(). 150. String title) { super(owner. 20)). 0. Listing 154: Zentrieren mit FlowLayout – Version 2 (aus Start_Layout. Wenn Sie den Layout-Manager neu erzeugen. Sie müssen lediglich von der Breite (respektive Höhe) des Containers die Breite (Höhe) der Komponente abziehen und das Ergebnis durch zwei teilen. übergeben Sie den vgapWert als drittes Argument nach Ausrichtung und hgap-Wert. p.

setLayout(null). int x = (dim. der horizontal und vertikal in einem Fenster zentriert wird. JFrame f. } public void actionPerformed(ActionEvent e) { setVisible(false). // Layout-Manager deaktivieren getContentPane(). } } Listing 155: Zentrieren eines Schalters in einem Dialogfenster ohne Layout-Manager (aus Start. btnOK.add(btnOK. null).setLocation(x.addActionListener(this). setSize(500. btnOK. Das nachfolgende Listing demonstriert dies anhand eines Schalters.getWidth())/2. setResizable(false). 0. 25)). btnOK.300). Listing 156: Zentrieren eines Schalters in einem Fenster. ständig nachjustiert werden. 200). // Schalter in ContentPane einfügen getContentPane(). setTitle("Komponenten zentrieren"). public class Start extends JFrame implements ComponentListener { JButton btn. soll sie zentriert bleiben. das ComponentListener-Interface zu implementieren und die Komponente in den Methoden componentShown() und ComponentResized() zu positionieren. 120). dessen Abmessungen durch den Benutzer verändert werden können (aus Start.btnOK.width .314 >> Komponenten in Fenster (Panel) zentrieren // Abmaße für Dialog festlegen setSize(370. muss die Position der Komponente.) GUI Zentrieren durch dynamische Berechnung Wenn sich die Größe des Containers während der Ausführung des Programms verändern kann. public Start() { // Hauptfenster einrichten f = this. // Schalter erzeugen JButton btnOK = new JButton("OK").java) (Forts. 100. Zu diesem Zweck ist es erforderlich.setBounds(new Rectangle(0.java) .getSize(). // Schalter horizontal zentrieren Dimension dim = this.

height-btn. . dessen Abmessungen durch den Benutzer verändert werden können (aus Start.setLocation(x. mit deren Hilfe Sie Swing-Komponenten mit Rahmen versehen können.width-btn. } public void componentHidden(ComponentEvent e) { } public void componentMoved(ComponentEvent e) { } . // Schalter erzeugen btn = new JButton("Dialog öffnen").) 122 Komponenten mit Rahmen versehen Im Package javax.getHeight())/2.getSize().. setDefaultCloseOperation(JFrame. sondern sich passende Rahmen-Objekte von den statischen create-Methoden der Klasse BorderFactory zurückliefern lassen.setBorder(BorderFactory. btn.>> GUI 315 getContentPane().java) (Forts. btn.. int x = (dim.y).border sind eine Reihe von Border-Klassen definiert.. } // Schalter bei erstem Erscheinen und jeder Größenänderung des // Fensters zentrieren public void componentShown(ComponentEvent e) { Dimension dim = getContentPane().getSize(). addComponentListener(this). int y = (dim. null). GUI . In der Regel werden Sie diese Klassen aber nicht direkt instanzieren.height-btn.getWidth())/2. int y = (dim.add(btn..BLACK)).setLayout(null).width-btn.setLocation(x.getHeight())/2.swing.y).createLineBorder(Color. int x = (dim. Listing 156: Zentrieren eines Schalters in einem Fenster. // Komponente mit schwarzer Umrandung als Rahmen aComponent. } public void componentResized(ComponentEvent e) { Dimension dim = getContentPane().EXIT_ON_CLOSE).getWidth())/2. Die so erzeugten RahmenObjekte weisen Sie dann mit der JComponent-Methode setBorder() Ihren Komponenten zu. // Schalter in ContentPane einfügen getContentPane().

neben dem Typ auch die Farben anzugeben. der unterschiedliche Farben für links und oben bzw. Color shadowOuter. GUI Rand der angegebenen Stärke. LineBorder Rahmen aus Linie der angegebenen Farbe und Stärke. int left. int bottom. createEtchedBorder(Color highlight. EtchedBorder Wie BevelBorder. erweckt aber den Eindruck einer »Gravierung«. innen liegenden Teile der aufgehellten bzw. createBevelBorder(int type) Als Typ übergeben Sie BevelBorder.RAISED oder EtchedBorder. Color highlightInner. Wenn Sie einen Layout-Manager verwenden.LOWERED). Das RahmenObjekt wählt als Farben dazu aufgehellte bzw. int thickness) BevelBorder Rahmen fester Stärke. Color shadow) Erlaubt es Ihnen.) createEmptyBorder() Null-Pixel-Rand createEmptyBorder(int top.LOWERED. createLoweredBevelBorder() Entspricht createBevelBorder(BevelBorder. neben dem Typ auch die Farben anzugeben.316 >> Komponenten mit Rahmen versehen Welche Rahmen gibt es? Tabelle 31 gibt Ihnen eine Übersicht über die vordefinierten Swingborder-Klassen und die zugehörigen create-Methoden von BorderFactory. createEtchedBorder() createEtchedBorder(int type) Als Typ übergeben Sie EtchedBorder. abgedunkelte Varianten der Hintergrundfarbe der Komponente. createBevelBorder(int type. Das RahmenObjekt wählt als Farben dazu aufgehellte bzw. Color shadowInner) Erlaubt die Angabe von zwei Farben für die jeweils außen bzw. um die Komponente hervorgehoben oder eingesenkt erscheinen zu lassen. Color highlight. um den Rahmen als hervorstehend oder eingesunken erscheinen zu lassen.RAISED oder BevelBorder. Color shadow) Erlaubt es Ihnen. Color highlightOuter. abgedunkelten Rahmenelemente.RAISED). werden Ihre Vorgaben überschrieben. abgedunkelte Varianten der Hintergrundfarbe der Komponente. Tabelle 31: Vordefinierte Swing-Rahmen . createLineBorder(Color color) createLineBorder(Color color. Color highlight. createRaisedBevelBorder() Entspricht createBevelBorder(BevelBorder.LOWERED. int right). der die Größe der Komponenten anpasst (beispielsweise GridLayout). Rahmenklasse EmptyBorder BorderFactory-Methode Erzeugt einen Abstand zwischen Inhalt und Außenumriss der Komponente (in der Hintergrundfarbe). (Ersetzt die früher übliche Angabe von Insets. Color shadow) createEtchedBorder(int type. rechts und unten verwendet. createBevelBorder(int type.

Color color) Ähnlich wie LineBorder. Font titleFont. createMatteBorder(int top.LEFT. Je nach Methode können Sie zudem die Titelausrichtung (TitledBorder. int titlePosition. TitledBorder. TitledBorder Blendet einen Titel in den Rahmen ein. createTitledBorder(Border border) Verwendet den als Argument übergebenen Rahmen und blendet in dessen oberen Rand einen leeren Titel ein. ein Muster (in Form einer Bilddatei) anzugeben. Color titleColor) Verwendet den angegebenen Rahmen und blendet in dessen oberen Rand den angegebenen Titel ein. TitledBorder.TOP.ABOVE_TOP. Font titleFont) createTitledBorder(Border border. int titlePosition.BELOW_TOP. TitledBorder.) mit einem EmptyBorder als Abstandshalter zwischen Dekorativrahmen und Komponenteninhalt.BOTTOM. createTitledBorder(Border border. Icon tileIcon) Erlaubt es Ihnen.TRAILING. mit dem der Rahmen gezeichnet wird.ABOVE_BOTTOM.CENTER. TitledBorder. int titleJustification.RIGHT. createMatteBorder(int top. TitledBorder.DEFAULT_JUSTIFICATION). TitledBorder. zwei Border-Objekte zu einem Rahmen zusammenzufügen. Border insideBorder) Tabelle 31: Vordefinierte Swing-Rahmen (Forts. createTitledBorder(String title) Verwendet einen EtchedBorder-Rahmen und blendet in dessen oberen Rand den angegebenen Titel ein. int titleJustification. GUI . Meist kombiniert man einen dekorativen Rahmen (LineBorder. int right. TitledBorder. int left. String title. nur dass Sie für jede Rahmenseite eine eigene Dicke festlegen können.LEADING. EtchedBorder etc. CompoundBorder Erlaubt es Ihnen. int titleJustification. TitledBorder.>> GUI 317 Rahmenklasse MatteBorder BorderFactory-Methode Farbiger Rahmen mit unterschiedlichen Linienstärken für die einzelnen Seiten sowie optional der Angabe eines Musters. int bottom. int left. String title) createTitledBorder(Border border. createCompoundBorder(Border outsideBorder. int bottom.DEFAULT_POSITION) sowie Schriftart und Farbe festlegen. int titlePosition) createTitledBorder(Border border. String title. die vertikale Position des Titels (TitledBorder. TitledBorder. String title.) Hi nwei s Eigene Rahmenklassen leiten Sie von AbstractBorder ab. TitledBorder. int right.

HAND_CURSOR).getPredefinedCursor(Cursor. den Sie sich – soweit es sich um einen vordefinierten Cursor handelt – am besten von einer der Cursor-Methoden getDefaultCursor(). GUI Abbildung 62: Der Rahmenkatalog des Start-Programms 123 Komponenten mit eigenem Cursor Um festzulegen. rufen Sie einfach die Component-Methode setCursor() auf und übergeben ihr den gewünschten Cursor.318 >> Komponenten mit eigenem Cursor Das Start-Programm zu diesem Rezept führt zu jedem Rahmentyp drei Varianten vor.setCursor(Cursor. Folgende Cursor-Konstanten sind in der Klasse Cursor definiert: CROSSHAIR_CURSOR CUSTOM_CURSOR DEFAULT_CURSOR E_RESIZE_CURSOR HAND_CURSOR MOVE_CURSOR N_RESIZE_CURSOR NE_RESIZE_CURSOR NW_RESIZE_CURSOR S_RESIZE_CURSOR SE_RESIZE_CURSOR SW_RESIZE_CURSOR TEXT_CURSOR W_RESIZE_CURSOR WAIT_CURSOR Tabelle 32: Vordefinerte Cursor-Konstanten .setCursor(cursor). oder in einer Zeile: aComponent.getPredefinedCursor(Cursor. getSystemCustomCursor() oder getPredefinedCursor() zurückliefern lassen: Cursor cursor = Cursor.HAND_CURSOR)). welcher Cursor über einer Komponente (oder einem Fenster) angezeigt werden soll. aComponent.

LIGHT_GRAY). müssen Sie für die Komponente einen Mouse.>> GUI 319 Abbildung 63: Cursor über Komponente 124 Komponenten mit Kontextmenü verbinden Grundsätzlich stellt die Einrichtung eines Kontextmenüs keine allzu große Herausforderung für den Programmierer dar: Das Kontextmenü (eine JPopupMenu-Instanz) wird analog zu dem Menü einer Menüleiste aus JMenuItem-Elementen aufgebaut und anschließend bei Bedarf durch Aufruf der JPopupMenuMethode show() angezeigt.und KeyPressed-Ereignisse überwachen: public class Start extends JFrame { private JScrollPane scrollpane. Unter Windows soll das Kontextmenü geöffnet werden. public Start() { // Hauptfenster konfigurieren setTitle("Datei-Drag für JTextArea"). des Textcursors) angezeigt werden. wenn der Anwender in der Komponente die rechte Maustaste drückt und wieder loslässt oder auf seiner Tastatur die Kontextmenü-Taste (soweit vorhanden) drückt. private JTextArea textpane.java GUI . Um allen drei Punkten gerecht zu werden. MouseReleased. Listing 157: Aus Start. private JPopupMenu contextmenu.setBackground(Color. Unter Linux soll das Kontextmenü geöffnet werden. getContentPane(). wie es auf den ersten Blick erscheint. Gerade der letzte Punkt ist aber nicht ganz so einfach zu implementieren. In jedem Fall sollte das Kontextmenü an der Position der Maus (bzw.und einen KeyAdapter registrieren und die MousePressed-. wenn der Anwender in der Komponente die rechte Maustaste drückt.

addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { textpane. } }).getComponent().show(e. e. JMenuItem paste = new JMenuItem("Einfügen"). // Öffnen beim Drücken der Kontextmenü-Taste (Windows) textpane.getComponent().paste(). } }).copy(). GUI // Kontextmenü mit JTextArea verbinden // Öffnen beim Drücken der rechten Maustaste textpane.getCaret().isPopupTrigger()) contextmenu.show(e. paste. scrollpane.getY()).addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent e) { if (e. textpane = new JTextArea(). // Kontextmenü mit Zwischenablagebefehlen für JTextArea aufbauen contextmenu = new JPopupMenu().) .addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { textpane.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { textpane. Listing 157: Aus Start. e. contextmenu.add(copy).java (Forts. } }). contextmenu.isPopupTrigger()) contextmenu. cut.getX(). e.getKeyCode() == KeyEvent. e.cut().add(paste).320 >> Komponenten mit Kontextmenü verbinden // Scrollbare JTextArea einrichten scrollpane = new JScrollPane(). copy. } public void mouseReleased(MouseEvent e) { if (e.VK_CONTEXT_MENU) { Point pos = textpane. JMenuItem copy = new JMenuItem("Kopieren").getMagicCaretPosition(). contextmenu.getX(). JMenuItem cut = new JMenuItem("Ausschneiden").getY()).add(cut).getViewport(). } }).add(textpane. null).addKeyListener(new KeyAdapter() { public void keyReleased(KeyEvent e) { if (e.

0. Wenn ja.) GUI In den mousePressed().. in die geklickt wurde..EXIT_ON_CLOSE). Abbildung 64: JTextArea mit Kontextmenü .y). } Listing 157: Aus Start. } } }).und mouseReleased()-Methoden prüfen Sie mittels e.CENTER). pos. pos. In der keyReleased()-Methode prüfen Sie mittels e.show(textpane. else contextmenu.>> GUI 321 if (pos == null) contextmenu. ermitteln Sie die Position des Textcursors in der Komponente und blenden das Kontextmenü am Ort des Cursors ein. ob die Kontextmenü-Taste gedrückt wurde (wobei e das KeyEvent-Objekt ist).show(textpane. blenden Sie das Kontextmenü am Ort des Mausklicks ein.java (Forts. getContentPane(). setDefaultCloseOperation(JFrame. ob das Kontextmenü angezeigt werden soll (wobei e das MouseEvent-Objekt ist). 0). H i n we i s Die getComponent()-Methode des MouseEvent-Objekts liefert die Komponente. die Methoden getX() und getY() liefern die Position der Maus relativ zum Ursprung der Komponente.x. } .isPopupTrigger(). BorderLayout. Wenn ja.add(scrollpane.getKeyCode().

dass Sie im Konstruktor des Fensters nach Instanzierung der Komponente für diese requestFocusInWindow() aufrufen. dass die Komponente den Fokus definitiv nicht erhalten wird. anhand der der Anwender erkennen könnte. dass sie ihn wahrscheinlich erhalten wird. die der aktuellen Komponente den Fokus übergibt und dazu notfalls auch das übergeordnete Fenster zum aktiven Fenster macht. können Sie beispielsweise so vorgehen. dass sie nicht den Fokus erhalten. dass eine entsprechende Kennzeichnung ein. müssen Sie für die betreffende JLabel-Instanz zuerst setFocusable(true) und dann requestFocusInWindow() aufrufen: lb. Möchten Sie eine solche Kennzeichnung vorsehen. ob die Komponente aktiviert ist (trifft beispielsweise nicht zu. können Sie nicht so vorgehen. wenn setVisible(false) für die Komponente aufgerufen wurde oder das Fenster mit der Komponente noch nicht aktiviert ist). Für JLabel-Komponenten gibt es keine vordefinierte grafische Kennzeichnung. Da das Fenster bei Ausführung des Konstruktors weder aktiv noch überhaupt sichtbar ist (die eingebetteten Komponenten folglich auch noch nicht sichtbar sind).requestFocusInWindow(). Ist der Rückgabewert dagegen false. wenn setEnabled(false) für die Komponente aufgerufen wurde). dass Sie die Fensterklasse das Interface FocusListener implementieren lassen und in den Methoden focusGained() und focusLost() dafür sorgen. führt auf verschiedenen Plattformen zu unterschiedlichen Ergebnissen und sollte daher eher nicht verwendet werden.322 >> Komponenten den Fokus geben 125 Komponenten den Fokus geben Um einer Komponente im aktiven Fenster den Fokus zu geben. welche Komponente bei Start des Programms oder Aktivierung des Fensters den Fokus erhält Wenn Sie festlegen möchten. Die Methode requestWindow(). GUI ob die Komponente den Fokus überhaupt entgegennehmen kann (trifft beispielsweise nicht für JLabel zu.setFocusable(true). . sondern nur. A c h t u ng A cht un g H i n we i s JLabel-Komponenten den Fokus zuweisen JLabel-Komponenten sind per Voreinstellung so konfiguriert. Wenn Sie dies ändern möchten. dass die Komponente den Fokus erhalten hat. hängt davon ab. dass eine JLabel-Komponente den Fokus innehat. ob die Komponente angezeigt werden kann und sichtbar ist (trifft beispielsweise nicht zu. ausgeblendet wird.requestFocusInWindow().bzw. Festlegen. welche Komponente eines Fensters beim Programmstart aktiviert ist. lb. geht die Fokusanforderung ins Leere. brauchen Sie einfach nur die Component-Methode requestFocusInWindow() aufzurufen: aComponent. Ob die Komponente danach den Fokus auch wirklich erhält. Liefert requestFocusInWindow() den Wert true zurück. heißt dies nicht. kann aber durch Aufruf von setFocusable(true) geändert werden). bedeutet dies.

} }). übergeht aber AWT-Komponenten.addWindowListener(new WindowAdapter() { public void windowOpened(WindowEvent e) { cb. } }).requestFocusInWindow(). Standard Wie ContainerOrderFocusTraversalPolicy. Als Antwort auf das Drücken des Schalters FOKUS AUF LABEL wird der Label-Komponente lb der Fokus zugewiesen (nachdem sie zuvor so konfiguriert wurde. wird durch eine so genannte Focus Traversal Policy festgelegt. Hinweis Das Start-Programm zu diesem Rezept demonstriert die drei oben angesprochenen Techniken: Als Antwort auf das Drücken des Schalters FOKUS AUF CHECKBOX 2 wird der CheckBoxKomponente cb2 der Fokus zugewiesen. beispielsweise durch Aufruf von setFocusable(true). überschreiben Sie die Methode windowActivated(): this. in der sie in den Container eingefügt wurden. dass sie den Fokus entgegennehmen kann). in der die Komponenten in einem Container auf diese Weise durchlaufen werden.>> GUI 323 Registrieren Sie stattdessen für das Fenster einen WindowListener und fordern Sie den Fokus in dessen windowOpened()-Definition an: this. 126 Die Fokusreihenfolge festlegen Auf den meisten Plattformen können Anwender mit Hilfe der (ÿ_)-Taste den Fokus von einer Komponente zur nächsten weitergeben. Bei Aktivierung des Fensters wird der Fokus automatisch an die zweite CheckBoxKomponente cb2 übergeben.) Tabelle 33: Vordefinierte Focus Traversal Policy-Klassen GUI . deren Peers auf der aktuellen Plattform keinen Fokus erhalten können.addWindowListener(new WindowAdapter() { public void windowActivated(WindowEvent e) { cb. sondern immer.requestFocusInWindow(). wenn der Anwender zu dem Fenster zurückkehrt und es aktiviert. Soll die besagte Komponente nicht nur beim Programmstart den Fokus erhalten. Die Reihenfolge. die Komponente wird im Programmcode explizit als fokussierbar deklariert. Focus Traversal Policy-Klassen ContainerOrderFocusTraversalPolicy DefaultFocusTraversalPolicy Beschreibung Durchläuft die Komponenten in der Reihenfolge. (Es sei denn.

der dem Konstruktor zu übergeben ist. müssen Sie eine Klasse von FocusTraversalPolicy ableiten und die Methoden getComponentAfter(). kann ein Container Komponenten und untergeordnete Container enthalten. für den keine eigene Focus Traversal Policy eingerichtet wurde. Position und Orientierung der Komponenten fest. // Positionszeiger // Konstruktor public ListFocusTraversalPolicy(Component.components = components. muss er sich spezieller.*.swing. plattformabhängiger Tastenkombinationen oder der Maus bedienen. der eine Focus Cycle Root ist. definiert eine Fokusreihenfolge für alle in ihm enthaltenen Komponenten (auch derjenigen in den untergeordneten Containern). getInitialComponent(). import java. Was aber ist eine Focus Cycle Root? Wie Sie wissen..) Ein Container.*. Die Klasse ListFocusTraversalPolicy beispielsweise übernimmt über den Konstruktor eine Auflistung oder ein Array von Komponenten und setzt den Fokus gemäß der Reihenfolge der Komponenten in diesem Array.. /** * Klasse. . // Array der Komponenten private int pos. Die untergeordneten Container können für die in ihnen eingebetteten Komponenten eine abweichende Fokusreihenfolge (Policy) festlegen. Container können durch Aufruf von setFocusCycleRoot(true) zu eben solchen gemacht werden. der der Fokus übergeben werden soll. Um in den Kreis einer anderen Focus Cycle Root zu springen. Listing 158: ListFocusTraversalPolicy reicht den Fokus gemäß der Reihenfolge der Komponenten in der übergebenen Auflistung weiter.) import java. Tabelle 33: Vordefinierte Focus Traversal Policy-Klassen (Forts.324 >> Die Fokusreihenfolge festlegen Focus Traversal Policy-Klassen SortingFocusTraversalPolicy LayoutFocusTraversalPolicy Beschreibung Legt die Reihenfolge mit Hilfe eines Comparators fest. die selbst wiederum Komponenten und untergeordnete Container enthalten können.*. (Tatsächlich setzt sie den Fokus natürlich nicht selbst.awt. sondern meldet über ihre Methoden jeweils die Komponente zurück. bleibt er im Kreis der Komponenten der aktuellen Focus Cycle Root. übernimmt die Focus Traversal Policy seiner übergeordneten Focus Cycle Root. GUI Eigene Focus Traversal Policies Um eine eigene Focus Traversal Policy zu implementieren. getDefaultComponent(). Ein Container.event. getComponentBefore(). die die Fokusreihenfolge festlegt */ public class ListFocusTraversalPolicy extends FocusTraversalPolicy { private Component[] components. Fenster sind per Voreinstellung Focus Cycle Roots. Legt die Reihenfolge anhand der Größe. bleiben aber weiterhin Teil des Fokuszyklus der Focus Cycle Root. import javax. components) { this.awt. getFirstComponent() und getLastComponent() überschreiben. Solange der Anwender die (ÿ_)-Taste drückt.

} // Referenz auf die Komponente zurückliefern. die bei Eintritt in einen // neuen Fokuszyklus als Erstes den Fokus erhalten soll public Component getDefaultComponent(Container cont) { return components[0]. } return components[pos].length) { pos = 0. } GUI Listing 158: ListFocusTraversalPolicy reicht den Fokus gemäß der Reihenfolge der Komponenten in der übergebenen Auflistung weiter. } else { ++pos.>> GUI 325 // Positionszeiger auf 1. die beim ersten Erscheinen // des Fensters den Fokus erhalten soll public Component getDefaultComponent(Window win) { return components[0]. } // Referenz auf die erste Komponente im Zyklus zurückliefern public Component getFirstComponent(Container cont) { return components[0]. } else { --pos.) . } // Referenz auf die Komponente zurückliefern. } // Referenz auf vorangehende Komponente zurückliefern public Component getComponentBefore(Container focusCycleRoot. Komponente setzen pos = 0. } return components[pos]. } // Referenz auf nachfolgende Komponente zurückliefern public Component getComponentAfter(Container focusCycleRoot. (Forts. Component active) { if(pos+1 == components.length. Component active) { if(pos == 0) { pos = components.

c2. p. indem Sie setFocusCycleRoot(true) aufrufen. c1).setFocusCycleRoot(true). 2.setFocusTraversalPolicy(ftp).setFocusTraversalPolicyProvider(true). indem Sie die in Schritt 1 erzeugte Instanz an die Methode setFocusTraversalPolicy() übergeben. p.) Einen Container als Focus Traversal Policy Provider einrichten 1. Deklarieren Sie den Container als Focus Traversal Policy Provider.setFocusTraversalPolicy(ftp). 3. } } Listing 158: ListFocusTraversalPolicy reicht den Fokus gemäß der Reihenfolge der Komponenten in der übergebenen Auflistung weiter. GUI Einen Container als Focus Cycle Root einrichten 1. // Den JPanel-Container p zum Focus Traversal Policy Provider machen ListFocusTraversalPolicy ftp = new ListFocusTraversalPolicy(rb3. rb1). (Forts. Deklarieren Sie den Container als Focus Cycle Root. 2. Legen Sie die Traversal Policy fest.length]. p.326 >> Fokustasten ändern // Referenz auf die letzte Komponente im Zyklus zurückliefern public Component getLastComponent(Container cont) { return components[components. // Den JPanel-Container p zur Focus Cycle Root machen ListFocusTraversalPolicy ftp = new ListFocusTraversalPolicy(c3. 127 Fokustasten ändern Sun empfiehlt. indem Sie die in Schritt 1 erzeugte Instanz an die Methode setFocusTraversalPolicy() übergeben. Erzeugen Sie eine Instanz der Focus Traversal Policy. Legen Sie die Traversal Policy fest. Erzeugen Sie die eine Instanz der Focus Traversal Policy. rb2. p. 3. dass unter Windows und Unix folgende Tasten für die Weiterleitung des Fokus verwendet werden: TextArea Nächste Komponente Vorangehende Komponente Drücken von: (Strg) + (ÿ_) Drücken von: (ª) + (Strg) + (ÿ_) Andere Komponenten Drücken von: (ÿ_) oder (Strg) + (ÿ_) Drücken von: (ª) + (ÿ_) oder (ª) + (Strg) + (ÿ_) Tabelle 34: Empfohlene und vordefinierte Tasten für die Fokusweitergabe (innerhalb eines Focus Cycle) . indem Sie setFocusTraversalPolicyProvider(true) aufrufen.

die zum untergeordneten 2. Die nachfolgende Klasse definiert drei statische Methoden.BACKWARD_TRAVERSAL_KEYS – Tasten. import java. die zur nachfolgenden KompoKeyboardFocusManager. Erweitern Sie die neue Tasten-Collection um weitere Tasten oder entfernen Sie bestehende Tasten. in bestimmten Fällen – beispielsweise auf Wunsch eines Kunden – auch andere Tasten für die Fokusweitergabe zu registrieren. Als ID übergeben Sie eine der folgenden Konstanten: nente springen KeyboardFocusManager.getFocusTraversalKeys(traversalCode)).awt.>> GUI 327 Trotzdem ist es möglich.DOWN_CYCLE_TRAVERSAL_KEYS – Tasten. Weisen Sie die neue Tasten-Collection mit setFocusTraversalKeys(int id. import java.UP_CYCLE_TRAVERSAL_KEYS – Tasten.*. reduzieren oder ersetzen kann.java – statische Hilfsmethoden zur Registrierung von Fokus-Traversal-Tasten GUI KeyboardFocusManager. 4. Listing 159: MoreGUI.) 3. Mit der Container-Methode getFocusTraversalKeys(int id) lassen Sie sich eine Set<AWTKeyStroke)-Collection der aktuell registrierten Fokus-Traversal-Keys zurückliefern. die zum übergeordneten . (Das Original-Set kann nicht verändert werden.util. int keyCode) { // Aktuelle Traversal-Tasten abfragen HashSet<AWTKeyStroke> keys = new HashSet<AWTKeyStroke>(c. die zur vorangehenden Komponente springen Fokus-Cycle wechseln Fokus-Cycle wechseln KeyboardFocusManager. Set<? extends AWTKeyStroke> keystrokes) dem Container zu. mit denen man den aktuellen Satz von Fokus-Traversal-Tasten erweitern. Erzeugen Sie eine Kopie der Tasten-Collection. 1. int traversalCode. Die Registrierung von Tasten für die Fokusweitergabe geschieht immer auf der Ebene eines Containers.*. public class MoreGUI { // Instanzbildung unterbinden private MoreGUI() { } /** * Fügt der Liste der Fokus-Traversal-Tasten eine weitere zu */ public static void addTraversalKey(Container c.FORWARD_TRAVERSAL_KEYS – Tasten.

// Neue Traversal-Tasten registrieren c. int traversalCode.0.328 >> Fokustasten ändern // Taste hinzufügen keys. } GUI /** * Tauscht die Liste der Fokus-Traversal-Tasten gegen eine Taste aus */ public static void replaceTraversalKey(Container c. int traversalCode. // Neue Traversal-Tasten registrieren c. keys).setFocusTraversalKeys(traversalCode.getFocusTraversalKeys(traversalCode)).getAWTKeyStroke(keyCode. keys. // Neue Traversal-Tasten registrieren c.) Das Start-Programm zu diesem Rezept enthält zwei Eingabefelder und zwei Schalter zum Testen der Fokusweitergabe. Listing 160: Aus Start.setFocusTraversalKeys(traversalCode.java – statische Hilfsmethoden zur Registrierung von Fokus-Traversal-Tasten (Forts.remove(AWTKeyStroke. keys).getAWTKeyStroke(keyCode. Über die Schalter können Sie die (Enter)-Taste als zusätzliche FokusTraversal-Tasten einrichten bzw.getAWTKeyStroke(keyCode. // <Return-Taste> als zusätzliche Fokus-Traversal-Taste einrichten JButton btnAdd = new JButton("<Return> hinzufügen").java . löschen. // Taste entfernen keys.false)). } } Listing 159: MoreGUI.0. keys). int keyCode) { // Neues Set erzeugen HashSet<AWTKeyStroke> keys = new HashSet<AWTKeyStroke>(1). int keyCode) { // Aktuelle Traversal-Tasten abfragen HashSet<AWTKeyStroke> keys = new HashSet<AWTKeyStroke>(c.setFocusTraversalKeys(traversalCode.false)).add(AWTKeyStroke.false)).0.add(AWTKeyStroke. } /** * Entfernt eine Taste aus der Liste der Fokus-Traversal-Tasten */ public static void removeTraversalKey(Container c.

addTraversalKey(contentPane. Listing 160: Aus Start. Listing 161: Aus Start.100. } }).getCurrentKeyboardFocusManager(). der beim Drücken der (Enter)-Taste den Fokus an die nächste Komponente weitergibt. btnRemove. indem Sie die (Enter)-Taste als Fokus-TraversalTaste für alle Komponenten in einem Container registrieren.getKeyCode() == KeyEvent.VK_ENTER). KeyEvent.100. aber viele Anwender wissen es zu schätzen.) 128 Eingabefelder mit Return verlassen Es widerspricht zwar den offiziellen Sun-Empfehlungen zur Fokusweiterleitung (siehe Rezept 126). // <Return-Taste> als Fokus-Traversal-Taste entfernen JButton btnRemove = new JButton("<Return> entfernen").>> GUI 329 btnAdd.FORWARD_TRAVERSAL_KEYS. haben Sie in Rezept 127 gesehen. GUI .185. contentPane.java (Forts.add(btnRemove). KeyboardFocusManager. dass Sie für die betreffenden Komponenten einen passenden KeyListener registrieren: aComponent.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { MoreGUI.java – KeyListener. } }). contentPane. H in we is Eine Liste der in KeyEvent definierten Tastencodes finden Sie im Anhang. Wie Sie dieses Verhalten implementieren.setBounds(10.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { MoreGUI.add(btnAdd).FORWARD_TRAVERSAL_KEYS.VK_ENTER). kfm = KeyboardFocusManager. btnAdd.VK_ENTER) { KeyboardFocusManager kfm.removeTraversalKey(contentPane. KeyboardFocusManager. Sollen nur Eingabefelder oder einzelne Komponenten durch Drücken der (Enter)-Taste verlassen werden.setBounds(205.25). KeyEvent.addKeyListener(new KeyAdapter() { public void keyReleased(KeyEvent e) { if (e.185. müssen Sie dagegen so vorgehen.25). btnRemove. wenn sie Eingabefelder nach der Bearbeitung durch Drücken der (Enter)-Taste verlassen können.

der direkt mit (Enter) oder (Esc) verlassen werden kann. . Listing 161: Aus Start. Abbildung 65: Dialog. gehen wir so vor. (Forts.focusNextComponent(e. } } }). der beim Drücken der (Enter)-Taste den Fokus an die nächste Komponente weitergibt. in der InputMap des JA-Schalters die Action-Objekte mit KeyStroke-Objekten für die gedrückten (Enter). dass er direkt nach dem Aufspringen wahlweise mit (Enter). dass wir dem JA-Schalter beim Öffnen des Dialogs den Fokus zuteilen. und die InputMap verknüpft diese Aktionen mit Tasten. Wie kann man diesen Dialog so implementieren.330 >> Dialoge mit Return (oder Esc) verlassen kfm. flüchtigen Blick gleich wieder schließen möchten. als Entsprechung für den NEIN-Schalter. wenn der Dialog dann durch Drücken der (Enter)-Taste beendet werden kann. In Java ist eine solche Funktionalität über den Umweg einer InputMap leicht zu implementieren.java – KeyListener. GUI 129 Dialoge mit Return (oder Esc) verlassen Nicht selten werden Anwender mit Dialogen konfrontiert. wenn die zugehörige Komponente den Fokus besitzt. oder mit (Esc). in der ActionMap des JA-Schalters die Action-Objekte eintragen. Die ActionMap speichert die Aktionen. die sie nach einem kurzen. Schön. die sowohl mit dem JAals auch dem NEIN-Schalter verbunden sind. die ausgeführt werden können.und (Esc)-Tasten verbinden. als Entsprechung für den JA-Schalter.getComponent()). verlassen werden kann? Da Tastaturaktionen nur ausgeführt werden. welche tastaturgesteuerten Aktionen für die Komponente ausgelöst werden können. lassen Sie sich eine Referenz auf den aktuellen KeyboardFocusManager zurückliefern und rufen dessen focusNextComponent()-Methode mit der aktuellen Komponente (die verlassen werden soll) als Argument auf.) Um den Fokus weiterzugeben. Als Beispiel betrachten Sie den Dialog aus Abbildung 65. die zusammen festlegen. Jede Swing-Komponente verfügt über eine ActionMap und eine InputMap.

setBounds(new Rectangle(190. 60. contentPane. btnNo = new JButton().put("yes".add(btnYes). btnNo. contentPane. Listing 162: Aus Start.getActionMap().setText("Ja").put(nokey. contentPane = (JPanel) getContentPane().setLayout(null). title). // Aktionen mit Tasten verbinden btnYes. import javax.setBounds(50. KeyStroke nokey = KeyStroke.false). lb. btnYes.setAction(no).*.*. import java. 25)). public DemoDialog(Frame owner.java . btnNo.und Yes-Schalter erzeugen btnYes = new JButton(). btnYes. btnYes. KeyStroke yeskey = KeyStroke.VK_ENTER. 100.getInputMap(). yes).250. NoAction no = new NoAction().add(btnNo).*.swing. String title) { super(owner.add(lb).setText("Nein"). 150).getKeyStroke(KeyEvent.awt. GUI // Hilfsvariablen für Schalter-Konfiguration YesAction yes = new YesAction(). 100. getContentPane().20.getActionMap().setBounds(new Rectangle(50.25).getInputMap().>> GUI 331 Hier der Code der Dialog-Klasse: import java.put(yeskey. 60. btnNo.VK_ESCAPE. // No. 25)). btnYes.event. setResizable(false). // Aktionen in ActionMap des Yes-Schalters eintragen btnYes. btnYes. JLabel lb = new JLabel("Soll Ihre Festplatte jetzt formatiert werden?"). private JButton btnYes. private JButton btnNo.put("no".getKeyStroke(KeyEvent.awt.0.false).0. no). "no").setAction(no). getContentPane(). setSize(350. class DemoDialog extends JDialog { private JPanel contentPane. "yes").

Für Labels (JLabel) gilt beispielsweise. der Sie true (für nichttransparent) und false (für transparent) übergeben können... JLabel lb = new JLabel("Titel").out.java (Forts. Allerdings dürfen Sie nicht erwarten. } }).setContentAreaFilled(false).. JButton btn = new JButton("Klick mich"). .out. setVisible(false). Stattdessen müssen Sie für Schalter deren Methode setContentAreaFilled() aufrufen.setOpaque(true). setVisible(false). } // Action-Klassen für Schalter private class YesAction extends AbstractAction { public void actionPerformed(ActionEvent e) { System. hängt von ihrer internen Implementierung ab. dass die Einstellung mit setOpaque() auch für jede Komponente zum gewünschten Ergebnis führt.) GUI 130 Transparente Schalter und nichttransparente Labels Die Transparenz von Swing-Komponenten wird über die Methode setOpaque() gesteuert.println("Schade. Über setOpaque() nimmt die Komponente nämlich lediglich Ihren Wunsch entgegen. Ob die Komponente diesen auch berücksichtigt. // nicht transparentes Label Für Schalter (JButton) gilt dagegen.332 >> Transparente Schalter und nichttransparente Labels // Dem Schalter beim Öffnen des Dialogs den Fokus zuweisen addWindowListener(new WindowAdapter() { public void windowOpened(WindowEvent e) { btnYes. // transparenter Schalter . .. btn.println("Krrrrrrrrrrrrrrr"). lb. dass sie per Voreinstellung nicht transparent sind und auch nicht mit setOpaque(false) auf transparente Darstellung umgestellt werden können. dass sie per Voreinstellung transparent sind und mit setOpaque(true) auf nichttransparente Darstellung umgestellt werden können. dann eben nicht"). } } } Listing 162: Aus Start. } } private class NoAction extends AbstractAction { public void actionPerformed(ActionEvent e) { System.requestFocusInWindow().

import java.awt.java GUI . muss die Komponente neu gezeichnet werden – beispielsweise indem Sie repaint() für das übergeordnete Fenster aufrufen. icon). } public TransparentButton(String text) { super(text). } public TransparentButton(String text. was – sofern alle Basisklassen dieser Regel folgen – letzten Endes zum Aufruf der JComponent-Version führt. import javax. Listing 163: TransparentButton.paintComponent(g). Die Klasse TransparentButton demonstriert dies anhand eines Schalters. sollten Sie grundsätzlich als erste Anweisung die geerbte paintComponent()-Version aufrufen.swing.. Auf diese Weise wird sichergestellt. public void paintComponent(Graphics g) { super. der im Gegensatz zu JButton mit Hilfe von setOpaque() zwischen transparenter und nichttransparenter Darstellung umgeschaltet werden kann. /** * Klasse für durchsichtige Schalter */ public class TransparentButton extends JButton { public TransparentButton() { super(). Wenn Sie die Basisklassenversion von paintComponent() nicht aufrufen.*.*. sollten Sie unbedingt in Ihrer paintComponent()-Version selbst mit isOpaque() abfragen. und dann dafür sorgen. ob der Benutzer der Komponente eine nichttransparente Darstellung wünscht. dass die Swing-Komponente hinsichtlich ihrer Transparenz das gewünschte Verhalten zeigt (sprich die setOpaque()-Einstellung berücksichtigt). Icon icon) { super(text. A c h tun g Damit die veränderte Darstellung auf dem Bildschirm sichtbar wird. dass der Hintergrund der Komponente komplett ausgefüllt wird – ansonsten kann es zu unschönen Artefakten kommen..>> GUI 333 Transparenz in eigenen Komponenten Wenn Sie eine eigene Swing-Komponente definieren und dabei paintComponent() überschreiben. } public TransparentButton(Icon icon) { super(icon). . } public TransparentButton(Action a) { super(a). wenn für die Komponente setOpaque(true) aufgerufen wird.

dass nach Umschaltung der Transparenz das Fenster mit repaint() neu gezeichnet wird. public class Start extends JFrame { private Image bgImage = null. private TransparentButton btn.NORTH) und einen TransparentButton-Schalter (am oberen Rand. javax. BorderLayout. Listing 164: Demo-Programm zur Transparenz .swing. 0. Das Label ist anfangs transparent. g. BorderLayout. java. } GUI // Komponente zeichnen lassen getUI().File. darf super.imageio.io. getHeight()).SOUTH).awt.this).ImageIO. javax. der Schalter nichttransparent. java.*. java.event. Beachten Sie. Dafür muss isOpaque() abgefragt und die * Komponente bei Bedarf nicht-transparent gezeichnet werden */ public void paintComponent(Graphics g) { // Hintergrund füllen. Durch Klick auf den Schalter kann die Transparenz beider Komponenten umgeschaltet werden.paint(g.*.*. getWidth(). private JLabel lb.paintComponent(g) * nicht aufgerufen werden.java (Forts. private JFrame frame. import import import import import import import java.awt.IOException.) Das Start-Programm zu diesem Rezept zeigt vor dem Hintergrund einer Küstenlandschaft ein Label (am oberen Rand.334 >> Transparente Schalter und nichttransparente Labels } /** * Um transparenten Schalter zu zeichnen.fillRect(0.awt.io. } } Listing 163: TransparentButton.Image.setColor(getBackground()). java. wenn nicht transparent if (isOpaque()) { g. // innere Klasse für ContentPane private class ContentPane extends JPanel { public ContentPane() { setLayout(new BorderLayout()).

// Hintergrundbild in Panel zeichnen if (bgImage != null) g. this). } } // Bilddatei laden try { bgImage = ImageIO.isOpaque()).drawImage(bgImage.paintComponent(g). this. . } } Listing 164: Demo-Programm zur Transparenz (Forts. BorderLayout. } catch(IOException ignore) { } setContentPane(new ContentPane()). setDefaultCloseOperation(JFrame.add(lb.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { lb.jpg")). btn.SOUTH).setOpaque(!lb. lb = new JLabel("Küstenansicht".300).add(btn. } public static void main(String args[]) { Start frame = new Start(). BorderLayout.) GUI public Start() { frame = this.setOpaque(!btn.setLocation(300.getHeight(). frame.setSize(500.CENTER). 0. getContentPane(). frame. } }). 0. btn. frame. SwingConstants.EXIT_ON_CLOSE). btn = new TransparentButton("Transparenz ändern").>> GUI 335 } public void paintComponent(Graphics g) { super.NORTH). getContentPane(). setTitle("Transparenz").isOpaque()).setVisible(true). frame.repaint(). this.read(new File("background.getWidth().300).

werden abgelehnt.336 >> Eingabefeld für Währungsangaben (inklusive InputVerifier) GUI Abbildung 66: Swing-Komponenten mit wechselnder Transparenz 131 Eingabefeld für Währungsangaben (inklusive InputVerifier) Abbildung 67: Fenster mit JMoneyTextField-Eingabefeld In diesem Rezept geht es um ein Eingabefeld. Wer sich im Dschungel der Swing-Komponenten ein wenig auskennt. welches Benutzereingaben als Währungsangaben formatiert (sprich als Gleitkommazahlen mit zwei Nachkommastellen zurückliefern kann). die nicht dem gewünschten Format entsprechen. das Eingabefeld behält seinen alten Wert und der Anwender kann das Eingabefeld nicht durch Drücken der (ÿ_)-Taste verlassen). d. dass sich für die Implementierung eines solchen Eingabefelds die Klasse JFormattedTextField als Basisklasse förmlich aufdrängt.h.. Benutzereingaben automatisch verifiziert (Eingaben. dass sie intern mit einem Formatierer (Instanz einer AbstractFormatter-Klasse) zusammenarbei- . JFormattedTextField-Eingabefelder unterscheiden sich von JTextField-Eingabefeldern dadurch. wird sicher zustimmen.

dessen Text unverändert zurückliefern. zwischen »Wert« und »Text« eines JFormattedTextField-Eingabefelds zu unterscheiden. /* Listing 165: Klasse des Eingabefelds für Währungsangaben (aus JMoneyTextField. java. den Text zu formatieren versucht. der entweder nach jeder Änderung. Die Aktualisierung des »Werts« kann durch Aufruf der Methode commitEdit() auch jederzeit vom Programmcode aus angestoßen werden.text. Die Klasse JMoneyTextField Nach diesen Vorbemerkungen zur Klasse JFormattedTextField kommen wir zum Code des JMoneyTextField-Eingabefelds. wird der formatierte Text zum neuen »Text« und auch der »Wert« des Eingabefelds wird aktualisiert. die Text unverändert in das Eingabefeld schreiben bzw. Typ des Objekts und Formatierer müssen zusammenpassen.>> GUI 337 ten.*. Double etc.swing. stellt JFormattedTextField noch zwei alternative setValue().Locale.) GUI Wichtig ist. java. spätestens aber bei Drücken der (Enter)-Taste oder beim Verlassen des Textfelds. ändert er den »Text«.*. Achtung /* * Formatiertes Textfeld für Währungsangaben * gibt den Fokus nur weiter. der im Eingabefeld zu sehen ist! (Siehe nachfolgende Erläuterung. Der von getValue() zurückgelieferte Wert ist nicht notwendigerweise der Text.*. können Objekte der Klasse Number und ihrer abgeleiteten Klassen.und getText()-Methoden. der im Eingabefeld angezeigt wird (entspricht dem Text des JTextField-Eingabefelds). import import import import javax. */ public class JMoneyTextField extends JFormattedTextField { private InputVerifier inVeri = new JMoneyTextFieldVerifier(). wenn die aktuelle Eingabe * korrekt formatiert ist.und getValue()-Methoden zur Verfügung: setValue() nimmt ein Objekt entgegen und übergibt es an den internen Formatierer. wie Integer. Der »Text« ist der String. der es in einen String verwandelt und im Eingabefeld anzeigt.util.text. Neben den üblichen setText().swing.java) . Arbeitet das Eingabefeld beispielsweise mit einem NumberFormatter zusammen. Tippt der Anwender etwas in das Eingabefeld ein. übergeben werden.. Ist die Formatierung möglich. getValue() wandelt umgekehrt den Wert des Eingabefeldes mit Hilfe des internen Forma- tierers in ein entsprechendes Objekt um (der Typ des Objekts hängt vom Formatierer ab) und liefert dieses zurück. Den geänderten Text übergibt die Komponente an den internen Formatierer. javax.

. } GUI /* * Konstruktor. setFormatterFactory(ff). // Formatierer-Factory für Währungsangaben erzeugen und registrieren AbstractFormatterFactory ff = new DefaultFormatterFactory(new NumberFormatter(formatOffer)). // InputVerifier registrieren setInputVerifier(inVeri).getCurrencyInstance(Locale. // Falsche Eingaben stehen lassen setFocusLostBehavior(JFormattedTextField.COMMIT).COMMIT). } } Listing 165: Klasse des Eingabefelds für Währungsangaben (aus JMoneyTextField.java) (Forts. setFormatterFactory(ff). // Formatierer-Factory für Währungsangaben erzeugen und registrieren AbstractFormatterFactory ff = new DefaultFormatterFactory(new NumberFormatter(formatOffer)).) Die von JFormattedTextField abgeleitete Klasse JMoneyTextField besteht aus zwei Konstruktoren. erzeugt Währungsfeld für angegebene Locale */ public JMoneyTextField(Locale loc) { // Formatierer für Textfeld erzeugen NumberFormat formatOffer = NumberFormat. die sich lediglich darin unterscheiden. während der zweite Konstruktor die zu verwendende Lokale als Argument entgegennimmt. // Falsche Eingaben stehen lassen setFocusLostBehavior(JFormattedTextField.getCurrencyInstance(loc).GERMANY). dass der erste Konstruktor ohne Argument aufgerufen wird und die Währungsangaben nach der Lokale für Deutschland formatiert.GERMANY */ public JMoneyTextField() { // Format für Textfeld erzeugen NumberFormat formatOffer = NumberFormat. erzeugt Währungsfeld für Locale. // InputVerifier registrieren setInputVerifier(inVeri).338 >> Eingabefeld für Währungsangaben (inklusive InputVerifier) * Konstruktor.

wird das Verhalten JFormattedTextField.COMMIT gewählt. bis der Anwender die Eingabe korrigiert hat. Eingabenüberprüfung mittels InputVerifier Die letzte Besonderheit der Komponente ist. Statt des Standardverhaltens (JFormattedTextField. Die shouldYieldFocus()-Methode ruft intern die verify()-Methode auf. die den Fokus daraufhin abgibt oder behält. welche entscheidet. wie die Komponente sich verhalten soll. die shouldYieldFocus()-Methode ihres InputVerifiers auf. rufen jedes Mal. bedeutet demnach. welches im Falle einer fehlerhaften Eingabe den alten Text einblendet. soll der InputVerifier false zurückliefern. wenn sie den Fokus verlieren. die Zahlenwerte gemäß der angegebenen Lokale als Währungsangaben formatiert (also mit zwei Nachkommastellen und Währungssymbol). Der InputVerifier für die Klasse JMoneyTextField soll prüfen. Wenn dies nicht der Fall ist. Auf der Basis dieser Instanz wird dann ein Formatierer-Factory erzeugt. A c htu n g Der InputVerifier-Mechanismus kontrolliert nur die Fokusweitergabe mittels der FokusTraversal-Tasten (siehe auch Rezept 127). ob der Fokus wirklich weitergegeben (Rückgabewert true) oder doch behalten werden soll (false). InputVerifier sind Klassen. für die mittels setInputVerifier() eine InputVerify-Instanz registriert wurde. Schließlich wird noch festgelegt. class JMoneyTextFieldVerifier extends InputVerifier { public boolean verify(JComponent input) { if (input instanceof JFormattedTextField) { // InputVerifier wurde für JFormattedTextField registriert // -> prüfe Eingabe mit Formatter des Textfelds Listing 166: InputVerifier für JFormattedTextField-Komponenten (aus JMoneyTextField. vom welchem die Komponente intern bei Bedarf die benötigten Formatierer-Instanzen bezieht.COMMIT_OR_REVERT). Der Rückgabewert wird von der shouldYieldFocus()-Methode an die Komponente weitergeleitet. eine Klasse von InputVerifier abzuleiten und die abstrakte verify()-Methode zu überschreiben. welches die fehlerhafte Eingabe (hoffentlich zur Nachbesserung durch den Anwender) stehen lässt. ob die vom Anwender eingetippte Eingabe dem gewünschten Währungsformat (inklusive Währungssymbol) entspricht.java) GUI . Der Anwender kann den Kontrollmechanismus jederzeit umgehen. damit die Komponente den Fokus behält. indem er mit der Maus auf eine andere Komponente klickt. wenn sie den Fokus verliert. Einen eigenen InputVerifier für eine Komponente zu schreiben. die von der abstrakten Basisklasse InputVerifier abgeleitet sind und von dieser zwei Methoden erben: boolean verify(JComponent input) boolean shouldYieldFocus(JComponent input) Swing-Komponenten. dass Sie einen InputVerifier verwendet.>> GUI 339 In den Konstruktoren wird dann zuerst eine NumberFormat-Instanz erzeugt.

in das Eingabefeld zu schreiben..h. } else { // InputVerifier wurde für irgendeine andere Komponente registriert // -> Eingabe durchlassen return true. JFormattedTextField.isEditValid()) { String txt = moneyTextField. dass dieser Text auch das korrekte Format hat.getText(). H i n we i s Verwendung des Eingabefelds Instanzen von JMoneyTextField werden grundsätzlich genauso behandelt wie JTextField. beispielsweise einen Anfangs. Um den Text im Eingabefeld abzufragen.B. InputVerifier sind weder auf JFormattedTextField-Komponenten noch auf die Überprüfung des Eingabeformats festgelegt. von 1 bis 100.java) (Forts. } } } Listing 166: InputVerifier für JFormattedTextField-Komponenten (aus JMoneyTextField.) GUI Der hier vorgestellte InputVerifier ist generisch für beliebige JFormattedTextField-Komponenten implementiert. if (formatter != null) { try { formatter.getText()). return true. Um einen beliebigen Text. d. dass neben dem Format auch gleich der Wertebereich (z. wenn Sie vom Programm aus Text in das Eingabefeld schreiben oder dessen Text auslesen wollen. Beispielsweise könnte man JMoneyTextFieldVerifier auch so implementieren. .stringToValue(ftf.000. Vorsicht ist lediglich geboten. in das Eingabefeld zu schreiben. verwenden Sie die Methode getText(). Um einen Wert. er liest den Text aus dem Eingabefeld und prüft ihn mit Hilfe des Formatierers.340 >> Eingabefeld für Währungsangaben (inklusive InputVerifier) JFormattedTextField ftf = (JFormattedTextField) input. moneyTextField = new JMoneyTextField().setValue(new Double(100)). rufen Sie vorab isEditValid() auf: if (moneyTextField. beispielsweise einen Hinweis auf das spezielle Eingabeformat. verwenden Sie die Methode setValue(). } } else return true. } catch (ParseException e) { return false. den auch die JFormattedTextField-Komponenten verwenden. moneyTextField.oder andere Komponenten.oder Beispielwert.000) überprüft wird. verwenden Sie die Methode setText(). Wenn Sie sichergehen wollen.getFormatter().AbstractFormatter formatter = ftf.

prüft mit Hilfe der Methode isEditValid(). wird der Text verarbeitet. GUI .commitEdit().getText()). ob der Text korrekt formatiert ist.20. btnSend.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if (tfOffer. der Komponente bei der Erzeugung einen gültigen Anfangs-»Wert« zuzuweisen und dann stets den »Wert« auszuwerten (der in der oben angesprochenen Situation dann aber nicht mit dem Text im Eingabefeld korrespondieren würde). .setBounds(30+120+30. Wenn Sie sichergehen wollen. Nicht unwichtig ist überdies die Frage. Wenn ja. Diese Strategie wird im Rezept 132 zum Datumseingabefeld verfolgt. diese Eingabe auswerten zu lassen.. die in diesem Rezept umgesetzt wird.setText("Eingabe ist keine korrekte " + "Währungsangabe").add(tfOffer). ansonsten wird eine Fehlermeldung ausgegeben.isEditValid()) { // Bestätigung anzeigen lbMessage.setBounds(125. // Textfeld zum Einlesen von Währungsangaben erzeugen tfOffer = new JMoneyTextField(LOCALE). dass der Wert auch aktuell ist (dem Text im Eingabefeld entspricht). indem er einen entsprechenden Schalter oder Menübefehl auswählt.100.add(btnSend).) Eine Möglichkeit wäre. wenn im Textfeld eine ungültige Eingabe steht und der Anwender versucht. wenn im Textfeld eine gültige Eingabe steht (was beispielsweise durch direkten Aufruf der verify()-Methode des InputVerifiers möglich ist). getContentPane(). Eine andere Möglichkeit wäre.25).>> GUI 341 Um den Wert des Eingabefelds abzufragen. btnSend. der lediglich die Fokusweitergabe mit den Fokus-Traversal-Tasten – üblicherweise die (ÿ_)-Taste – kontrolliert. wie das Programm reagieren soll.180. Eine dritte Technik. rufen Sie vorab commitEdit() auf: moneyTextField.setText("Danke für Ihr Angebot von " + tfOffer. } else { // Fehlermeldung anzeigen lbMessage.setValue(new Double(100)). // Eingabe in Textfeld auswerten JButton btnSend = new JButton("Abschicken"). die entsprechenden GUI-Komponenten zur Auswertung der Eingabe zu deaktivieren und erst freizugeben. } Listing 167: Aus Start. tfOffer. tfOffer.. (Er umgeht also den InputVerifier.getValue().java – tfOffer ist eine JMoneyTextField-Komponente.150.25). getContentPane(). verwenden Sie die Methode getValue(). { Double value = moneyTextField.

den Inhalt des Eingabefelds sich als Date-Objekt zurückliefern zu lassen. Date-Objekte im Eingabefeld anzuzeigen und umgekehrt.342 >> Eingabefeld für Datumsangaben (inklusive InputVerifier) // Meldungslabel sichtbar machen lbMessage..MaskFormatter und stellt ergänzend zwei Methoden getDate() und setDate() zur Verfügung.java – tfOffer ist eine JMoneyTextField-Komponente. Listing 167: Aus Start.text. es dem Programmierer erlaubt.text. Nur leider bietet dieser Formatierer dem Anwender nicht die Sicherheit und den Komfort einer Eingabemaske. sofern sie als Formatierer mit einem JFormattedTextField-Eingabefeld kombiniert wird. Die folgende Implementierung verwendet daher einen javax. als Date-Objekt zurückliefern. (Forts. die den »Wert« des Eingabefelds auf der Basis eines Date-Objekts setzen bzw. H in we is Hintergrundinformationen zu JFormattedTextField finden Sie in Rezept 131.h.swing. das dem Anwender eine Maske für die Eingabe von Datumsangaben anzeigt.setVisible(true). Wie auch im vorhergehenden Rezept bietet sich für die Implementierung eines solchen Eingabefelds die Klasse JFormattedTextField als Basisklasse an. Es gibt in den Tiefen der Java-Bibliothek auch eine passende Formatierer-Klasse.swing.) 132 Eingabefeld für Datumsangaben (inklusive InputVerifier) GUI Abbildung 68: Fenster mit JDateTextField-Eingabefeld In diesem Rezept geht es um ein Eingabefeld. wenn es einen korrekten Datumswert enthält). . javax. Benutzereingaben automatisch verifiziert (d. die. } }).DateFormatter. es akzeptiert nur Zahlen und kann erst dann durch Drücken der (ÿ_)-Taste verlassen werden.

PLAIN . dass die Installation eines DateFormatter als Default-Formatierer und eines MaskFormatter als Edit-Formatierer wegen der unterschiedlichen Value-Klassen erheblich schwieriger ist.*. javax. sei angemerkt. private int alignment = JTextField. GUI H i nwe i s public class JDateTextField extends JFormattedTextField { private static final String DATE_PATTERN = "##. /* * Konstruktor */ public JDateTextField() { setHorizontalAlignment(alignment). setFormatterFactory(ff).swing. Locale. } catch (ParseException e) { System.*. } // Formatierer-Factory für Datumsmaske erzeugen und registrieren AbstractFormatterFactory ff = new DefaultFormatterFactory(fm).*. java.awt.yyyy". // Falsche Eingaben stehen lassen setFocusLostBehavior(JFormattedTextField. setFont(font).CENTER.err.Font.*. javax. // Platzhalter für noch nicht gefüllte Stelle festlegen fm. Die Klasse JDateTextField import import import import import java.MM. private Font font = new Font("Serif".println("ERROR: Kein Formatierer"). // InputVerifier registrieren setInputVerifier(inVeri).swing. Listing 168: Die Klasse JDateTextField .####".text.text.18).GERMANY).*. private MaskFormatter fm.setPlaceholderCharacter('-'). die bereits mit JFormattedTextField und der Klasse AbstractFormatterFactory vertraut sind. java.##. private InputVerifier inVeri = new JDateTextFieldVerifier().util.COMMIT). try { // Formatierer für die Datumsmaske erzeugen fm = new MaskFormatter(DATE_PATTERN).>> GUI 343 Für Leser. private SimpleDateFormat df = new SimpleDateFormat("dd.

####"). wo er noch wie viele Stellen ausfüllen muss (Aufruf von setPlaceholderCharacter()). new ParsePosition(0)). die sich für jede Instanz nach Bedarf anpassen lassen). Für jeden Platzhalter muss ein Zeichen eingetippt werden! . Masken sind Strings.format(d)). wird der Formatierer für die Datumseingabemaske als Instanz der Klasse MaskFormatter erzeugt. } /* * Datum als Wert des Eingabefeldes eintragen */ public void setDate(Date d) { setValue(df. die sich aus folgenden Zeichen zusammensetzen können:2 Zeichen # A U L ? * ' Beschreibung Platzhalter für eine Ziffer Platzhalter für einen Buchstaben oder eine Ziffer Platzhalter für Kleinbuchstaben (Kleinbuchstaben werden automatisch in Großbuchstaben umgewandelt) Platzhalter für Großbuchstaben (Großbuchstaben werden automatisch in Kleinbuchstaben umgewandelt) Platzhalter für beliebigen Buchstaben Platzhalter für beliebiges Zeichen Escape-Zeichen wird unverändert in die Maske übernommen (kann vom Anwender nicht überschrieben werden) sonstiges Zeichen Tabelle 35: Platzhalter für MaskFormatter-Masken2 MaskFormatter schreibt für jeden Platzhalter ein Leerzeichen in das Eingabefeld. Als Argument übernimmt der MaskFormatter-Konstruktor die gewünschte Maske.##.) GUI Nachdem der Konstruktor der Klasse JDateTextField Ausrichtung und Schriftart des Textes im Eingabefeld festgelegt hat (wobei diese Einstellungen natürlich nur Empfehlungen sind. } } Listing 168: Die Klasse JDateTextField (Forts.parse((String) getValue(). 2.344 >> Eingabefeld für Datumsangaben (inklusive InputVerifier) } /* * Wert des Eingabefeldes als Datum zurückliefern */ public Date getDate() { return df. hier die String-Konstante DATE_PATTERN (gleich "##. Der JDateTextField-Konstruktor ersetzt das Leerzeichen als Platzhalterzeichen durch den Bindestrich. damit der Anwender besser erkennen kann.

Der MaskFormatter kann allerdings nur sicherstellen. zu dem wir gleich im nächsten Abschnitt kommen.parseInt(text. nach der Vorgabe eines Date-Objekts setzen. if (text != null && text. // Eingabe-Maske SimpleDateFormat df = new SimpleDateFormat("dd. String text = ftf.##. ob Datumsangabe korrekt JFormattedTextField ftf = (JFormattedTextField) input. die wie folgt definiert ist: class JDateTextFieldVerifier extends InputVerifier { public boolean verify(JComponent input) { boolean returnvalue = false. wird für diese Aufgabe kein eigener InputVerifier benötigt.GERMANY).substring(3. das eingangs der Klassendefinition als Feld df definiert wurde und auf die String-Maske für den MaskFormatter abgestimmt ist. die den internen Wert des JDateTextField-Eingabefelds als Date-Objekt zurückliefern bzw. Für die korrekte Umwandlung sorgt das SimpleDateFormat-Objekt. if ( (month (month (month (month (month (month (month == == == == == == == 1 2 2 3 4 5 6 && && && && && && && day <= 31) || !date.parseInt(text. nicht aber dass die Ziffern auch ein gültiges Datum ergeben. Zu diesem Zweck registriert JDateTextField als InputVerifier eine Instanz der Klasse JDateTextFieldInputVerifier. Neben dem Konstruktor definiert die Klasse noch zwei Methoden getDate() und setDate(). if (input instanceof JFormattedTextField) { // InputVerfifier wurde für JFormattedTextField registriert // -> prüfe.isLeapYear(year) && day <= 28) || date. static final String DATE_PATTERN = "##. Integer month = Integer.length() == 10) { try { Integer day = Integer. if (day > 0 && month > 0 && year > 0) { GregorianCalendar date = new GregorianCalendar().text. // Formatierer Locale. Eingabenüberprüfung Da im Falle des JDateTextField-Eingabefelds bereits der MaskFormatter sicherstellt.substring(6.yyyy".isLeapYear(year) && day <= 29) || day <= 31) || day <= 30) || day <= 31) || day <= 30) || Listing 169: InputVerifier für die Klasse JDateTextField GUI .>> GUI 345 Anschließend kann ein Formatierer-Factory mit dem MaskFormatter als Formatierer erzeugt und registriert werden. dass die Eingaben im korrekten Format vorliegen.5)).length())). dass die korrekte formatierte Anzahl Ziffern eingegeben wird.parseInt(text.2)). Die letzte Anweisung ist die Einrichtung des InputVerifiers.####".MM. Integer year = Integer.getText().substring(0.

Der Anwender kann die Positionen im Datum dann direkt überschreiben oder löschen (woraufhin das Platzhalterzeichen erscheint) und ersetzen.MEDIUM. bis der Anwender die Eingabe korrigiert hat.. Locale. (Eine Typumwandlung ist nicht nötig. Nach der Instanzierung des Eingabefelds: // Textfeld zum Einlesen von Datumsangaben erzeugen tfDate = new JDateTextField().setText(tfDate. initialisiert das Programm das Eingabefeld mit dem aktuellen Datum. .getValue() + " wurde gespeichert"). ob die vom Anwender eingetippte Eingabe einem korrekten Datum entspricht. Wenn der Anwender den Abschicken-Schalter drückt. } }).GERMANY). wird der Wert des Eingabefelds abgefragt (getValue()-Aufruf) und zur Kontrolle ausgegeben. . } } } GUI return returnvalue. damit die Komponente den Fokus behält. da der »Wert« des Eingabefelds wegen des MaskFormatters vom Typ String ist. lbMessage. } } } catch (NumberFormatException e) { return false.. tfDate. was dank der setDate()Methode von JDateTextField bequem zu erledigen ist: // Aktuelles Datum als Vorgabe in Eingabefeld schreiben Date d = new Date().) btnSend. } } Listing 169: InputVerifier für die Klasse JDateTextField (Forts.getDateInstance(DateFormat.346 >> Eingabefeld für Datumsangaben (inklusive InputVerifier) (month == (month == (month == (month == (month == (month == returnvalue 7 && day <= 31) || 8 && day <= 31) || 9 && day <= 30) || 10 && day <= 31) || 11 && day <= 30) || 12 && day <= 31) ) { = true.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { DateFormat df = DateFormat. lbMessage.) Der InputVerifier für die Klasse JDateTextField prüft.setDate(d). liefert der InputVerifier false zurück. Verwendung des Eingabefelds Das Start-Programm zu diesem Rezept demonstriert die Verwendung des JDateTextField-Eingabefelds.setVisible(true). Ist dies nicht der Fall.

ob die aktuelle Eingabe gültig ist. der bei jeder Bewegung des Textcursors mit Hilfe des InputVerifiers des Eingabefelds prüft. Wird eine Drag-Operation initiiert. .) Der TransferHandler der Quellkomponente muss die Daten in ein Transferable-Objekt verpacken. (Hinweis: Die Drag-Daten werden nicht von der Quellkomponente an den TransferHandler übergeben.addCaretListener(new CaretListener() { public void caretUpdate(CaretEvent e) { // Eingabe mit Hilfe des InpuVerfiers der Komponente prüfen JDateTextFieldVerifier verifier = (JDateTextFieldVerifier) tfDate.getInputVerifier(). werden die Daten beim Loslassen der Maus eingefügt. und den Schalter entsprechend aktiviert oder deaktiviert.setEnabled(false). if (verifier. der für den zu übertragenden Datentyp ausgelegt ist. fordert die Komponente ihren TransferHandler auf. Wenn ja. GUI 133 Drag-and-Drop für Labels Drag-and-Drop ist eine mausgesteuerte Übertragung von Daten zwischen Komponenten (gegebenenfalls über Anwendungsgrenzen hinweg). Etliche Swing-Komponenten unterstützen bereits von sich aus Drag-and-Drop.setEnabled(true). Für eine Drag-and-Drop-Datenübertragung zwischen zwei Komponenten müssen folgende Voraussetzungen erfüllt sein: Die Quellkomponente muss bei Initiierung einer Drag-Operation durch den Anwender die Daten exportieren. (Zur Erinnerung: Der InputVerifier kontrolliert nur die Fokusweitergabe mittels der Fokus-TraversalTasten. wie er die Daten von der Komponente bekommt. In Java bedeutet dies. die Daten entweder als MOVE-Aktion (die Daten werden in der Quellkomponente danach gelöscht) oder als COPY-Aktion (die Daten werden kopiert) zu übertragen. dass die Komponente über einen TransferHandler verfügen muss.verify(tfDate)) { // Ist Eingabe korrekt -> Schalter für Auswertung aktivieren btnSend. Die Zielkomponente muss ebenfalls über einen TransferHandler verfügen. ob die Daten im Transferable-Objekt in einem Format vorliegen. } else { // Ist Eingabe ungültig -> Schalter für Auswertung deaktivieren btnSend. } } }). tfDate. das die Komponente akzeptiert. der überprüft. wie man verhindert.) Das Start-Programm registriert zu diesem Zweck einen CaretListener für das Eingabefeld. dass der Anwender mit der Maus den Abschicken-Schalter anklickt. sondern der TransferHandler muss so implementiert sein.>> GUI 347 Bleibt noch die Frage zu klären. dass er weiß. ohne das Eingabefeld korrekt bearbeitet zu haben.

2.und datenspezifisch. Solange die Labels nur zur Präsentation statischen Textes verwendet werden. das Drag-and-Drop unterstützt"). Um sie für eine Komponente einzuschalten. dass sie eine bestimmte Art von Daten (den Data Flavor) für eine bestimmte Komponente ex. Das Schwierigste an der Installation eines Drag-and-Drop-Mechanismus ist in der Regel die Implementierung eines passenden TransferHandlers. . sie werden so implementiert. müssen Sie setDragEnabled(true) für die Komponente aufrufen: JTextArea ta = new JTextArea("Textfeld. Mit Hilfe von if-Abfragen können aber auch TransferHandler geschrieben werden. d. Label erzeugen lb = new JLabel("Label.h. ist dies auch kein Manko. Drag-and-Drop für Labels JLabel besitzt keine vorinstallierte Drag-and-Drop-Unterstützung.. TransferHandler für Texteigenschaft erzeugen und bei der Komponente registrieren lb.und importieren können.348 >> Drag-and-Drop für Labels Komponente JColorChooser JEditorPane JFileChooser JFormattedTextField JList JPasswordField JTable JTextArea JTextField Drag-Unterstützung Ja (nur COPY) Ja Ja (nur COPY) Ja Ja (nur COPY) Nein Ja (nur COPY) Ja Ja Ja Ja (nur COPY) Drop-Unterstützung Ja Ja Nein Ja Nein Ja Nein Ja Ja Ja Nein GUI JTextPane JTree Tabelle 36: Vorinstallierte Drag-and-Drop-Unterstützung für Swing-Komponenten A c h t u ng Die Drag-Unterstützung ist standardmäßig nicht aktiviert. Ein Beispiel hierfür ist die JavaKlasse TransferHandler.setDragEnabled(true). 3. die mehrere Datentypen für verschiedene Komponenten verarbeiten. müssen Sie die Drag-and-Drop-Unterstützung selbst nachinstallieren. die JavaBean-Eigenschaften zwischen Komponenten austauschen kann... Grundsätzlich sind TransferHandler komponenten. Beim Drücken der Maus Drag-Daten exportieren lassen . Mit ihrer Hilfe wird die Programmierung einer Drag-and-Drop-Unterstützung für Labels fast zum Kinderspiel. // Drag-Support für JTextArea aktivieren ta.setTransferHandler(new TransferHandler("text")). In Fällen. (Ein Beispiel für die Implementierung eines eigenen TransferHandlers finden Sie in Rezept 134. den Text eines Labels mit anderen Textkomponenten auszutauschen. das Drag-and-Drop unterstützt").) 1. wo Sie dem Anwender aber doch einmal erlauben wollen.

müssen Sie einen eigenen TransferHandler für JTextArea schreiben. auch Dateien per Drop importiert. TransferHandler th = c. th. Eigener TransferHandler für JTextArea Eigene TransferHandler werden von der Klasse TransferHandler abgeleitet. nicht etwa für Textdateien.>> GUI lb. 349 Das in Schritt 2 erzeugte TransferHandler-Objekt unterstützt sowohl den Drag-Export als auch den Drop-Import von Text. in das gerade ein Textfragment aus einer JTextArea-Komponente eingefügt wird (Screenshot vom Start-Programm zu diesem Rezept) 134 Datei-Drop für JTextArea-Komponenten (eigener TransferHandler) JTextArea gehört zu den Swing-Komponenten. dass Ihre Anwender auch die Inhalte von Textdateien in eine JTextArea einfügen können. Wenn Sie möchten. Strings aus anderen Komponenten (auch anderen Anwendungen) können daher ohne weiteres Zutun des Programmierers in JTextArea-Instanzen per Drop abgelegt werden.COPY). die ja erhalten bleiben sollte.exportAsDrag(c. Während für den Drop-Import nichts weiter zu tun ist. der neben der gewohnten Funktionalität (Drag und Drop von Strings). wobei stets der gesamte Text im Label exportiert oder durch die Drop-Daten ersetzt wird.getSource(). TransferHandler. H in we is Abbildung 69: Label. die über eine vorinstallierte Drag-and-DropUnterstützung verfügen (siehe Rezept 133).getTransferHandler(). e. muss der Export explizit angestoßen werden (siehe Schritt 3). GUI . } }). Dies gilt jedoch nur für Strings.addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent e) { JComponent c = (JComponent) e.

swing.awt. p1 = null. H in we i s In Java 6 wurde den Methoden canImport() und importData() Überladungen an die Seite gestellt.*. import import import import import import import /** * Transfer-Handler für JTextArea-Komponenten. Transferable data. javax.*. Die Klasse TransferHandler.datatransfer. Die Transfer-Informationen können über das TransferHandler. um dem Entwickler alle relevanten Transfer-Informationen in gebündelter Form zur Verfügung stellen zu können.awt.*.TransferSupport-Objekt abgefragt oder (zum Teil) geändert werden.event. um bei Drop feststellen zu können. Um die Drop-Unterstützung anzupassen. // // // // // // Drag-Quelle. die standardmäßig Drag-and-Drop unterstützen.swing.*. javax.BasicTextUI. ob Quelle und Ziel identisch sind. DataFlavor[] transferFlavors) boolean importData(JComponent comp. java. Stattdessen ist FileAndStringTransferHandler auf Drag-and-Drop von Strings und Textdateien für JTextArea-Komponenten spezialisiert. java. wird bei String-Drag (Methode createTransferable()) gespeichert.util. // Anfang und Ende zu exportierender // Drag-Daten private JTextArea source. java.350 >> Datei-Drop für JTextArea-Komponenten (eigener TransferHandler) A c h tun g GUI Wie für alle Swing-Komponenten.setDragEnabled(true). int action) Die nachfolgende Implementierung orientiert sich an der TransferHandler-Klasse TextTransferHandler aus javax. dass die Drag-Unterstützung explizit eingeschaltet werden muss: textarea. Erst danach können markierte Textstellen mit der Maus per Drag verschoben und an anderer Stelle per Drop eingefügt werden. java.*.*. ohne jedoch wie diese verschiedene GUIKomponenten und Data Flavors zu unterstützen.text.io.swing.plaf.awt. die als einziges Argument ein Objekt der inneren Klasse TransferHandler. Wenn dann auch die Drop-Position im Bereich der . java. gilt auch für JTextArea.TransferSupport übernehmen.basic. überschreiben Sie die Methoden: int getSourceActions(JComponent c) Transferable createTransferable(JComponent c) void exportDone(JComponent source. * der Dateien und Strings verarbeitet */ class FileAndStringTransferHandler extends TransferHandler { Position p0 = null.*. Transferable t) Um die Drag-Unterstützung anzupassen. überschreiben Sie die Methoden: boolean canImport(JComponent comp.TransferSupport wurde ebenfalls in Java 6 eingeführt.

der für die Target-Komponente geeignet ist. if(hasFileFlavor(flavors)) return true. Eine wirklich saubere Lösung ist dies allerdings nicht. der in die Komponente eingefügt werden kann (Datei oder Text). noch dadurch die falsche Registrierung verhindert werden kann. da weder der Parameter eine sinnvolle Verwendung findet. können bei jeder SwingKomponente mittels der Methode setTransferhandler() registriert werden. Damit kommen wir zur Methode canImport(). Daten per Drag aufzunehmen. canImport() – der Anwender zieht Drag-Daten über die Komponente. GUI TransferHandler. wandelt sich der Cursor über der Komponente in den Drop-Cursor. private boolean shouldRemove. importData() – der Anwender versucht. indem man den Konstruktor mit einem JTextArea-Parameter definiert. dass exportDone() die Drag-Daten löscht 351 FileAndStringTransferHandler() { } Die Klasse FileAndStringTransferHandler prüft zu diesem Zweck in den Methoden createTransferable() – der Anwender versucht. fragt. */ public boolean canImport(JComponent c. Wenn Sie eine TransferHandler-Klasse schreiben. wenn der Anwender Drag-Daten über die Komponente zieht. // // // // Wird auf false gesetzt. Man könnte ihm einen zusätzlichen Hinweis geben. Liefert die Methode true zurück.o. die von der Klasse TransferHandler abgeleitet sind. Drag-Daten in der Komponente per Drop abzu- legen. /** * Prüft mit Hilfe der private Methoden hasFileFlavor() und * hasStringFlavor(). Im Falle einer falschen Registrierung kann dann nicht viel Schaden entstehen – außer dass sich der Programmierer. warum die Drag-and-Drop-Unterstützung nicht funktioniert. ob es sich auch wirklich um eine JTextArea-Komponente handelt. ob sich hinter der Komponente c auch wirklich // eine JTextArea-Komponente verbirgt if (!(c instanceof JTextArea)) return false. wenn es sich um eine JTextArea-Komponente handelt und die Drag-Daten von einem Data Flavor sind. .).>> GUI // Drag-Auswahl liegt. wenn Ihre TransferHandler-Klasse für die falsche Komponente registriert wird. ob die angebotenen Daten in einem Data Flavor * angeboten werden. muss nichts // verändert werden. wenn DropPosition im Drag-Bereich liegt (s. DataFlavor[] flavors) { // Vorab prüfen. die auf eine bestimmte Art Komponente spezialisiert ist. der FileAndStringTransferHandler für die falsche Komponente registriert hat. müssen Sie sich daher Gedanken machen. die automatisch aufgerufen wird. was zu tun ist. Die vorliegende Implementierung liefert nur dann true zurück. um zu verhindern.

} GUI Legt der Anwender die Daten ab. In diesem Fall wird kein Drag-and-Drop durchgeführt. siehe weiter unten. Ansonsten werden die Drag-Daten in der Zielkomponente an der Drop-Position eingefügt.und Zielkomponente identisch sind (Drag und Drop innerhalb derselben Komponente) und die Drop-Position innerhalb der für den Drag ausgewählten Markierung liegt. ob nicht zufälligerweise Quell. versucht die Methode. return false. Transferable t) { // // // // if Vorab prüfen. } // Referenz c in den Typ JTextArea umwandeln JTextArea target = (JTextArea) c. (Und in der Quellkomponente werden die Daten von exportDone().stringFlavor)) return true.getTransferDataFlavors())) { // Transferable-Objekt enthält eine (oder mehrere) Dateien. wird die importData()-Methode aufgerufen. Handelt es sich um Dateien. . i < flavors.length. ihren Inhalt einzulesen und an der Einfügeposition in die JTextArea-Komponente zu schreiben. t.equals(DataFlavor. try { if (hasFileFlavor(t.) /** * versucht die Daten aus dem Transferable-Objekt t in der Komponente * c abzulegen */ public boolean importData(JComponent c.equals(DataFlavor.getTransferDataFlavors()) ) { return false. (!(c instanceof JTextArea) || !canImport(c. prüft die Methode zuerst. i++) { if (flavors[i]. ob sich hinter der Target-Komponente c auch wirklich eine JTextArea-Komponente verbirgt und ob die angebotenen Daten in einem Data Flavor angeboten werden. Diese stellt zuerst den Data Flavor fest.length. i < flavors. } private boolean hasStringFlavor(DataFlavor[] flavors) { for (int i = 0.352 >> Datei-Drop für JTextArea-Komponenten (eigener TransferHandler) if(hasStringFlavor(flavors)) return true. } return false. gelöscht. i++) { if (flavors[i]. Handelt es sich um einen String. } private boolean hasFileFlavor(DataFlavor[] flavors) { for (int i = 0. die erste Datei zu öffnen. der für die Target-Komponente geeignet ist.javaFileListFlavor)) return true. } return false.

// Dateiinhalt in String lesen int countBytes = 0. GUI . java.toString()).err. verschiebe nur. countBytes). 0. } finally { if (in != null) in.replaceSelection(str). } catch (IOException e) { System. } String str = (String) t.read(bytesRead)) > 0) str. } } else // // // // if (hasStringFlavor(t. target.close().getCaretPosition() >= p0. wenn die Einfügeposition außerhalb des zu verschiebenden Textes liegt 353 if((target == source) && (target.getOffset())) { shouldRemove = false. return true.stringFlavor).util. if (files.getTransferData(DataFlavor.List files = (java. try { in = new FileReader(f). target. while( (countBytes = in.javaFileListFlavor).getOffset()) && (target. return true.getTransferData(DataFlavor.getCaretPosition() <= p1.>> GUI // Lese die erste Datei und füge ihren Inhalt in die // Target-Komponente ein FileReader in = null.List) t.println(f + " kann nicht eingelesen werden").append(bytesRead. } return true. StringBuilder str = new StringBuilder(). char[] bytesRead = new char[512].size() > 0) { // nur erste Datei lesen File f = (File) files.get(0).replaceSelection(str.util.getTransferDataFlavors())) { Transferable-Objekt enthält eine (oder mehrere) Dateien Wenn Quelle und Ziel identisch sind.

Außerdem speichert sie Anfang und Ende der Textpassage im JTextArea-Dokument.getDocument().getSelectionStart(). /** * erzeugt ein Transferable-Objekt mit den zu übertragenden Drag-Daten */ protected Transferable createTransferable(JComponent c) { // Vorab prüfen.und Endposition des markierten Textes // im Dokument merken Document doc = source.err. public int getSourceActions(JComponent c) { return COPY_OR_MOVE. } } GUI Aufgabe der Methode getSourceActions() ist es anzuzeigen.err. return new StringSelection(data).getSelectionEnd().err. ob sich hinter der Komponente c auch wirklich // eine JTextArea-Komponente verbirgt und if (!(c instanceof JTextArea)) return null.getSelectedText(). } . source = (JTextArea) c. } catch (IOException e) { System.354 >> Datei-Drop für JTextArea-Komponenten (eigener TransferHandler) } } catch (UnsupportedFlavorException e) { System. int end = source. } Die Methode createTransferable() erzeugt aus der markierten Textstelle die Drag-Daten. ob die Drop-Position innerhalb des DragBereichs liegt. } else { try { // Anfangs.println("Text kann nicht verschoben werden.println("Drag-Daten werden nicht unterstützt").createPosition(start). } return false.println("I/O-Exception"). int start = source. dass die Komponente COPYAktionen (für Dateien) und MOVE-Aktionen (für Strings) unterstützt. } catch (BadLocationException e) { System. String data = source. if (start == end) { return null. p1 = doc. um später beim Drop (in importData()) prüfen zu können."). p0 = doc.createPosition(end). } shouldRemove = true.

p1.getOffset() != p1.setDragEnabled(true).h.p0. getContentPane(). // Scrollbare JTextArea einrichten scrollpane = new JScrollPane(). Listing 170: Aus Start.println("Text kann nicht gelöscht werden. } } } source = null. in die Sie den Inhalt beliebiger Textdateien per Drag-and-Drop einfügen können.java GUI .getOffset()). public class Start extends JFrame { private JScrollPane scrollpane. aus Sicht des Anwenders wird der String verschoben. } catch (BadLocationException e) { System. scrollpane. int action) { if (shouldRemove && (action == MOVE)) { if ((p0 != null) && (p1 != null) && (p0.>> GUI 355 Die Methode exportDone() wird automatisch zum Abschluss einer Drop-Aktion ausgeführt. textpane = new JTextArea(). null).add(textpane. Die Klasse FileAndStringTransferHandler nutzt dies.getViewport(). public Start() { // Hauptfenster konfigurieren setTitle("Datei-Drag für JTextArea").getOffset())) { try { JTextArea source = (JTextArea) c. source. // Drag-Support für JTextArea aktivieren textpane. private JTextArea textpane.LIGHT_GRAY).getDocument(). um nach dem Einfügen eines Strings (Drop) den String an der Originalposition in der Quelle zu löschen – d.remove(p0.").err.getOffset() .getOffset(). Transferable data. /** * Am Ende einer String-DROP-Aktion den Text in der Quelle löschen.setBackground(Color. */ protected void exportDone(JComponent c. } } Das Start-Programm zu diesem Rezept erzeugt eine JTextArea..

png").EXIT_ON_CLOSE). import java.*.swing. setDefaultCloseOperation(JFrame.add(scrollpane.java (Forts.*.URL tmp = Start. Aus dem zurückgelieferten URL-Objekt erzeugen Sie mit Hilfe der Toolkit-Methode createImage() das gewünschte Image-Objekt.setTransferHandler(new FileAndStringTransferHandler()).356 >> Anwendungssymbol einrichten // TransferHandler für Dateien und Text registrieren textpane.awt.EXIT_ON_CLOSE).createImage(tmp)).awt. public class Start extends JFrame { public Start() { // Hauptfenster konfigurieren setTitle("Swing-Grundgerüst"). dass der Dateiname automatisch um den Paketpfad der Anwendung erweitert wird. BorderLayout.*.LIGHT_GRAY).event. Um ein Bild aus einer Bilddatei als Anwendungssymbol einzurichten. getContentPane().) GUI 135 Anwendungssymbol einrichten GUI-Anwendungen verfügen üblicherweise über ein Anwendungssymbol. } Listing 171: Anwendungssymbol einrichten .class. if (tmp != null) setIconImage(Toolkit. setDefaultCloseOperation(JFrame. können Sie davon profitieren. welches Sie setIconImage() übergeben.getDefaultToolkit(). das quasi als Logo der Anwendung fungiert und vom Betriebssystem in verschiedenen Kontexten angezeigt wird (beispielsweise in der Titelleiste des Hauptfensters oder in der Taskleiste). laden Sie das Bild und übergeben Sie es der JFrame-Methode setIconImage(). Wenn Sie die Bilddatei mit Hilfe der Class-Methode getResource() laden. getContentPane(). // Anwendungssymbol einrichten java. } } Listing 170: Aus Start. import java.CENTER).getResource("icon.net.setBackground(Color. import javax.

GUI .add(tb. btn.setToolTipText("Neu").300).NORTH).add(btn). // Schalter für Symbolleiste JButton btn = new JButton(new ImageIcon("resources/New16.) Abbildung 70: Anwendung mit Ausrufezeichen als Anwendungssymbol 136 Symbole für Symbolleisten Symbole für Symbolleisten sind in der Regel 16 mal 16 Pixel groß und haben einen transparenten Hintergrund (GIF.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { // Ereignisbehandlungscode } }).setVisible(true). die Sie wiederum in den NORTH-Bereich des BorderLayouts der ContentPane einfügen: // Symbolleiste JToolBar tb = new JToolBar().>> GUI 357 public static void main(String args[]) { Start frame = new Start().gif")).oder PNG-Format.setSize(500. frame. btn.setLocation(300. frame. Aus der Symboldatei erzeugen Sie eine ImageIcon-Instanz. BorderLayout. Diese übergeben Sie als Argument dem JButton. Den Schalter selbst fügen Sie schließlich in die Symbolleiste (Instanz von JToolBar) ein. Tipp Anwendungssymbole sollten möglichst 16x16 oder 24x24 Pixel groß sein und einen transparenten Hintergrund haben. getContentPane().300). frame. tb. kein JPEG).oder JToggleButton-Konstruktor. } } Listing 171: Anwendungssymbol einrichten (Forts.

. tb. dass sie in Originalgröße gut zu erkennen sind. btn.setMaximumSize(new Dimension(2.. btn. . sep.addActionListener(fileNewAction). Die Symbole müssen nicht nur aussagekräftig und intuitiv zu deuten sein.. JSeparator sep = new JSeparator(SwingConstants.add(btn).add(btn).sun. btn. Meist wird man daher die Erstellung der Symbole Grafikern überlassen oder auf bereits vorhandene Symbole zurückgreifen.setToolTipText("Neu").add(Box.200)). // Symbolschalter für Datei-Menübefehle btn = new JButton(new ImageIcon("resources/New16.. tb.createHorizontalStrut(3)).add(sep).) Zur Gruppierung der Symbolschalter können Sie Abstandshalter (Rückgabewert der statischen Methode Box.java) GUI .addActionListener(editCutAction).setToolTipText("Ausschneiden").358 >> Symbole für Symbolleisten Die Kreation von Symbolen ist eine Kunst für sich. was oft nur durch geschickten Einsatz von Anti-Aliasing zu erreichen ist. tb. aus der Sie die einzelnen Grafiken mit jedem gängigen ZIP-Programm extrahieren können.createHorizontalStrut(3)). Sun stellt selbst eine reiche Auswahl von Symbolen für die verschiedensten Befehle zur Verfügung.com/developer/techDocs/hi/repository/ herunterladen. (Die Archivdatei enthält eine jar-Datei.VERTICAL). // Gruppierung mit Abständen und Separator tb.gif")).add(Box.createHorizontalStrut(int)). . Trennlinien (Instanzen von JSeparator) oder beides einfügen: // Symbolleiste erzeugen protected JToolBar createToolBar() { JToolBar tb = new JToolBar(). // Symbolschalter für Bearbeiten-Menübefehle btn = new JButton(new ImageIcon("resources/Cut16. Sie können die Symbolsammlung als Archivdatei von der Webseite http://java. JButton btn. btn. } Listing 172: Aufbau einer Symbolleiste (aus ProgramFrame.gif")). sie müssen auch so gezeichnet werden. return tb. tb.

in der alle zum Aufbau der Menüleiste benötigten Informationen in Textform gespeichert sind. Worum es in diesem Rezept geht. einer Mnemonic-Taste für den Zugriff in Kombination mit der [Alt]-Taste (setMnemonic(int)).h.>> GUI 359 137 Menüleiste (Symbolleiste) aus Ressourcendatei aufbauen Heutzutage verfügt nahezu jedes GUI-Programm über eine Menüleiste. der nach einem Gleichheitszeichen auf den Namen folgt.FileQuit FileLabel=Datei FileMnemonic=D . dem eigentlichen Programm. Dieser werden die einzelnen Menüs als Instanzen der Klasse JMenu hinzugefügt. d. wie sich der Aufbau von Menüleisten zumindest zum Teil automatisieren lässt.. das sich der MenuFactory-Klasse bedient und ansonsten nur noch die Ereignisbehandlung für die Menübefehle beisteuern muss. Die Ressource für die Menüleiste heißt menuBar und zählt die Namen der Ressourcen für die einzelnen Menüs auf: menuBar=File Edit Info Für jedes der aufgeführten Menüs gibt es eine gleichnamige Menüressource. mit deren Hilfe die Menüleiste basierend auf den Informationen in der Ressourcendatei aufgebaut wird. Die Ressourcendatei Ressourcendateien sind letzten Endes Eigenschaftendateien. schauen Sie sich die Datei ProgramFrame. einer MenuFactory-Klasse. einem Symbol (üblicherweise das Symbol des zugehörigen Schalters aus der Symbolleiste) und natürlich einer ActionListener-Instanz zur Ereignisbehandlung (addActionListener(ActionListener)) verbunden werden. Die fertige Menüleiste wird mit Hilfe der JFrame-Methode setJMenuBar() in das Hauptfenster eingebettet. Wahrscheinlich sind Sie mit all dem bereits vertraut – falls nicht. meist inklusive passender Symbolleiste(n). Jeder Menübefehl kann mit einem Titel (setLabel(String) oder Konstruktor). GUI einem Tastaturkürzel (setAccelerator(KeyStroke)).java aus dem Rezept »Symbole für Symbolleisten« an. ist die Frage. über den sie vom Programm aus abgerufen werden kann. Der hier gewählte Ansatz beruht auf drei Komponenten: einer Ressourcendatei. jede Ressource besteht aus einem Namen. in die wiederum JMenuItem-Objekte für die eigentlichen Menübefehle eingefügt werden. und einem Wert. die die Namen der Ressourcen für die Befehle im Menü aufzählt (Trennzeichen werden durch einen Bindestrich kodiert) sowie Ressourcen für den Titel (xxxLabel) und den Mnemonic-Buchstaben (xxxMnemonic) des Menüs: File=FileNew FileOpen FileSave FileSaveAs . Eine Menüleiste ist in Java eine Instanz der Klasse JMenuBar. Die Ressourcendatei für die Menüleiste ist wie folgt aufgebaut.

und Info-Menü: # Ressourcendatei mit Menü # Menüleiste menuBar=File Edit Info # Datei-Menü # File=FileNew FileOpen FileSave FileSaveAs . Mnemonic-Buchstaben (xxxMnemonic). FileOpenMnemonic=F FileOpenAccelerator=Strg+O FileOpenSymbol=resources/open. GUI Die Ressourcennamen für die Menüs und Menübefehle lauten so. da die Klasse MenuFactory die Ressourcen sonst nicht findet.gif FileOpenTooltip=Öffnen FileOpenDescription=Öffnet ein vorhandenes Dokument. Tastaturkürzel (xxxAccelerator).gif FileNewTooltip=Neu FileNewDescription=Erstellt ein neues Dokument. Die von mir vorgegebene Konvention zum Aufbau der Ressourcennamen sieht wie folgt aus: Die Menüleistenressource heißt menuBar. FileNewLabel=Neu FileNewMnemonic=N FileNewAccelerator=Strg+N FileNewSymbol=resources/new.gif FileNewTooltip=Neu FileNewDescription=Erstellt ein neues Dokument.. Die Ressourcendatei zu diesem Rezept definiert eine Menüleiste mit Datei-.FileQuit FileLabel=Datei FileMnemonic=D # Menübefehle für Menü Datei FileNewLabel=Neu FileNewMnemonic=N FileNewAccelerator=Strg+N FileNewSymbol=resources/new. Diese Konvention ist bei Änderungen oder bei der Definition eigener Ressourcendateien unbedingt einzuhalten.. die Titel (xxxLabel).360 >> Menüleiste (Symbolleiste) aus Ressourcendatei aufbauen Für jeden Menübefehl gibt es Ressourcen. Symbol (xxxSymbol). Die Namen von Eigenschaften setzen sich zusammen aus dem Namen der Ressource und einem Suffix für die Eigenschaft (Label. FileOpenLabel=Öffnen. Listing 173: Program. Bearbeiten.). Tooltip-Text (xxxToolTip) und Beschreibung (xxxDescription) definieren. wie in der Menüleiste und den Menüressourcen angegeben. Mnemonic etc.properties – die Ressourcendatei .

EditCopyLabel=Kopieren EditCopyMnemonic=K EditCopyAccelerator=Strg+C EditCopySymbol=resources/copy.gif FileSaveTooltip=Speichern FileSaveDescription=Speichert das aktuelle Dokument. FileQuitLabel=Beenden FileQuitMnemonic=B FileQuitDescription=Beendet die Anwendung. # Info-Menü # Info=InfoInfo InfoLabel=Info InfoMnemonic=I Listing 173: Program.gif EditCopyTooltip=Kopieren EditCopyDescription=Kopiert die Markierung in die Zwischenablage. EditPasteLabel=Einfügen EditPasteMnemonic=E EditPasteAccelerator=Strg+V EditPasteSymbol=resources/paste. FileSaveAsMnemonic=U FileSaveAsDescription=Speichert das aktuelle Dokument unter neuem Namen.>> GUI 361 FileSaveLabel=Speichern FileSaveMnemonic=S FileSaveAccelerator=Strg+S FileSaveSymbol=resources/save.properties – die Ressourcendatei (Forts.) GUI .gif EditCutTooltip=Ausschneiden EditCutDescription=Schneidet die Markierung aus.gif EditPasteTooltip=Einfügen EditPasteDescription=Fügt den Inhalt der Zwischenablage ein. # Bearbeiten-Menü # Edit=EditCut EditCopy EditPaste EditLabel=Bearbeiten EditMnemonic=B # Menübefehle für Menü Bearbeiten EditCutLabel=Ausschneiden EditCutMnemonic=D EditCutAccelerator=Strg+X EditCutSymbol=resources/cut. FileSaveAsLabel=Speichern unter...

/** * Klasse zur Erstellung von Menü.*.net. .getBundle(res. loc).URL. private JToolBar toolBar = null. Locale loc) { try { resources = ResourceBundle.*. der Menüleiste. import java. Listing 173: Program.und Symbolleisten aus Ressourcendateien eingelesen werden.swing. H in we is Beachten Sie.util. private HashMap<String. private HashMap<String. JMenuItem> menuItems = new HashMap<String. Unterschiedliche Instanzen der Klasse können unterschiedliche Menüsysteme repräsentieren. public MenuFactory(String res.Locale. JButton>(). import javax. import import import import import java. Die Klasse definiert private Felder zum Abspeichern der Ressourcendatei. der Symbolleiste sowie zweier Hashtabellen.362 >> Menüleiste (Symbolleiste) aus Ressourcendatei aufbauen # Menübefehle für Menü Info InfoInfoLabel=Info InfoInfoMnemonic=I InfoInfoDescription=Zeigt den Info-Dialog an.awt. in denen die Menübefehle und Symbolleistenschalter unter ihren Ressourcennamen abgelegt sind.util. JButton> toolBarButtons = new HashMap<String.properties – die Ressourcendatei (Forts.*. java.util.MissingResourceException. auch wenn der Buchstabe im Titel klein geschrieben wird. private JMenuBar menuBar = null. java.) GUI Die Klasse MenuFactory Mit Hilfe der Klasse MenuFactory können Menü.event. Bei der Instanzierung der Klasse übergeben Sie dem Konstruktor den Namen der Ressourcendatei (gegebenenfalls einschließlich relativem Pfad) sowie die zu verwendende Lokale.ResourceBundle. java.und Symbolleiste aus Ressourcendatei */ public class MenuFactory { private ResourceBundle resources.HashMap. dass der Mnemonic-Buchstabe immer als Großbuchstabe anzugeben ist.awt. import java. JMenuItem>().util. java.

if (m != null) menuBar.getString(name + "Label")). Welche Menübefehle das Menü enthalten soll. die eine Referenz auf die JMenuBarMenüleiste zurückliefert. wird von getMenuBar() aufgerufen protected JMenu createMenu(String name) { JMenu m = null. wobei Letzterer nicht unbedingt definiert sein muss. ansonsten wird der Ressourcenname des Befehls an die protected-Methode createMenuItem() weitergereicht.err. // Menüs erzeugen for (String s : menuNames) { JMenu m = createMenu(s). Die fertigen Menüs werden in die Menüleiste eingefügt.getString("menuBar"). // Menü erzeugen.println("Menüressource nicht verfügbar!"). /** * Liefert eine Referenz auf die Menüleiste zurück * Beim ersten Aufruf wird die Menüleiste erzeugt */ public JMenuBar getMenuBar() { if ( menuBar == null) { menuBar = new JMenuBar(). Für einen Bindestrich wird eine Trennlinie in das Menü eingefügt. try { // Ressourcenstring für Menüleiste abfragen und in // String-Array mit Namen der Menüs aufsplitten String buf = resources. } } 363 Die für den Benutzer wichtigste Methode ist getMenuBar(). try { // Menü erzeugen m = new JMenu(resources.exit(1). System.println("Ressourcendatei nicht verfuegbar!").exit(1). } } catch (MissingResourceException mre) { System. String[] menuNames = buf. Existiert noch gar keine Menüleiste. System.err.>> GUI } catch (MissingResourceException mre) { System. GUI . } Die Methode createMenu() übernimmt als Argument den Namen einer Menüressource und liefert als Ergebnis das fertige Menü zurück. entnimmt die Methode dem Wert der Menüressource. Dazu greift die Methode auf die menuBar-Ressource zu.add(m). } } return menuBar. Titel und Mnemonic-Buchstabe des Menüs werden der Ressourcendatei entnommen. wird sie automatisch aufgebaut.split(" "). zerlegt deren Wert in die Namen der einzelnen Menüs und lässt diese von der protected-Methode createMenu() erzeugen.

exit(1). // Menübefehl erzeugen. try { // Menübefehl erzeugen String label = resources.addSeparator(). Die restlichen Angaben sind optional. String[] menuItemNames = buf. } Die Methode createMenuItem() schließlich erzeugt die einzelnen Menübefehle als Instanzen von JMenuItem. System. die ja das Programm beisteuert – der Ressourcendatei entnommen. else { JMenuItem mi = createMenuItem(s). } return m. muss also in der Ressourcendatei definiert sein. Zum Schluss wird die JMenuItem-Instanz für später in die Hashtabelle menuItems eingetragen und als Ergebniswert zurückgeliefert. try { mnemo = resources.println("Menüressource nicht verfügbar!"). GUI // Ressourcenstring für Menü abfragen und in String-Array // mit Namen der Menübefehle aufsplitten String buf = resources. mi = new JMenuItem(label). try { mnemo = resources.equals("-")) m.getString(name).err.364 >> Menüleiste (Symbolleiste) aus Ressourcendatei aufbauen // Mnemonic String mnemo = null. } } } catch (MissingResourceException mre) { System.getString(name + "Mnemonic"). Der Titel (xxxLabel) ist obligatorisch.setMnemonic(mnemo. .getString(name + "Mnemonic").split(" ").charAt(0)). m. // Menübefehle erzeugen for (String s : menuItemNames) { if (s.add(mi). // Mnemonic String mnemo = null. Die Informationen für die Konfiguration der Menübefehle werden – mit Ausnahme der Ereignisbehandlung. } catch (MissingResourceException mre) { // mnemo bleibt null } if (mnemo != null) m.getString(name + "Label"). wird von createMenu() aufgerufen protected JMenuItem createMenuItem(String name) { JMenuItem mi = null.

// Tastaturkürzel String accel = null.err.getString(name + "Symbol"). wenn Symbole in Menübefehlen unerwünscht String image = null.. } catch (MissingResourceException mre) { // image bleibt null } if (image != null) { URL url = this.charAt(accel. die weiter unten im letzten Abschnitt dieses Rezepts abgedruckt sind. } // Auskommentieren.setMnemonic(mnemo. try { accel = resources.getKeyStroke( (int) accel. } 365 Als Pendant zu den Methoden zur Erzeugung der Menüleiste gibt es Methoden zum Aufbau einer Symbolleiste.println("Menüressource nicht verfügbar!"). mi.>> GUI } catch (MissingResourceException mre) { // mnemo bleibt null } if (mnemo != null) mi. } protected JButton createToolBarButton(String name) { . System.CTRL_MASK)... } GUI .charAt(0)).getString(name + "Accelerator"). try { image = resources. } } // Menübefehl in Feld menuItems für späteren Zugriff abspeichern menuItems.getClass(). protected JToolBar getToolBar() { . } catch (MissingResourceException mre) { System.setIcon(new ImageIcon(url)).. Event. } return mi.length()-1).getResource(image).put(name. mi).exit(1). } catch (MissingResourceException mre) { // accel bleibt null } if (accel != null) { KeyStroke ks = KeyStroke.setAccelerator(ks). if (url != null) { mi.

setJMenuBar(menuBar).. ... Die von getMenuBar() zurückgelieferte Referenz übergibt sie an setJMenuBar(). . JButton> getToolBarButtons() { return toolBarButtons. die mit Hilfe von MenuFactory ihre Menüleiste aufbauen möchte. Die Fensterklasse zu diesem Rezept definiert für jeden Befehl eine eigene ActionListener-Klasse: public class ProgramFrame extends JFrame { . Hierfür gibt es eine Vielzahl von Möglichkeiten (siehe Rezept 119). private JMenuBar menuBar.getDefault()). . Sie ruft die getMenuBar()-Methode des MenuFactory-Objekts auf. wobei sie den Pfad zur Ressourcendatei und die zu verwendende Lokale übergibt. die Referenzen auf die Hashtabellen für die Menübefehle und die Symbolleistenschalter zurückliefern. Sie definiert ein private-Feld für das MenuFactory-Objekt (und gegebenenfalls auch Felder für die Menüleiste). } } GUI Verwendung in einem Programm Eine Frame-Klasse. sprich mit ActionListener-Objekten.. 3..366 >> Menüleiste (Symbolleiste) aus Ressourcendatei aufbauen Zu guter Letzt definiert die Klasse zwei Methoden... JMenuItem> getMenuItems() { return menuItems.. 4.getMenuBar(). public HashMap<String. die ihre Menüleisten (Symbolleisten) mit Hilfe von MenuFactory erzeugen. . um die Menüleiste aufbauen zu lassen. Locale. 2. Sie erzeugt im Konstruktor eine Instanz von MenuFactory. bequem auf die einzelnen Menübefehle und Symbolleistenschalter zugreifen – beispielsweise um sie mit einer Ereignisbehandlung zu kombinieren. Sie verbindet die einzelnen Menübefehle mit Ereignisbehandlungen... // Menü aufbauen und als Hauptmenü des Fensters einrichten mf = new MenuFactory("resources/Program". geht wie folgt vor: 1. } public HashMap<String. public class ProgramFrame extends JFrame { private MenuFactory mf. menuBar = mf. Über diese Hashtabellen können Programme. public ProgramFrame() { .

mi. # Symbolleiste # # Die Namen der Schalter müssen gleich den Namen der Menübefehle sein! toolBar=FileNew FileOpen FileSave .>> GUI // ActionListener-Objekte für Menübefehle private FileNewAction fileNewAction = new FileNewAction().out. sprich Bild und ToolTip-Text.get("FileNew"). die die Symbolleiste aufbaut.EditCut EditCopy EditPaste Alle weiteren Informationen zur Konfiguration der Schalter. für die auch Symbolschalter in der Symbolleiste angezeigt werden sollen.. die Namen der Menübefehlressourcen aufzählt. // innere ActionListener-Klassen für Menübefehle class FileNewAction implements ActionListener { public void actionPerformed(ActionEvent e) { System.. } } .get("FileOpen"). heißt getToolBar() und ist analog zur Methode getMenuBar() aufgebaut: GUI . // Ereignisbehandlung für Menübefehle HashMap<String. } } class FileOpenAction implements ActionListener { public void actionPerformed(ActionEvent e) { System. JMenuItem> menuItems = mf. mi = menuItems. 367 und verbindet diese im Konstruktor mit den zugehörigen Menübefehlen: public ProgramFrame() { .. mi. .println(" Datei / Oeffnen").. Die MenuFactory-Methode.getMenuItems(). JMenuItem mi. . mi = menuItems.. werden den Angaben zu den Menübefehlen entnommen (siehe Abschnitt »Die Ressourcendatei«)..addActionListener(fileOpenAction). Symbolleisten Analog zur Menüleiste können auch Symbolleisten aus Ressourcendateien erzeugt werden..out. Die Ressourcendatei zu diesem Rezept definiert zu diesem Zweck eine eigene Ressource namens toolBar.addActionListener(fileNewAction). private FileOpenAction fileOpenAction = new FileOpenAction().println(" Datei / Neu")..

split(" "). die analog zu createMenuItem() aufgebaut ist.1)). die in die Symbolleiste eingefügt werden kann.1.getClass(). wird von getToolBar() aufgerufen protected JButton createToolBarButton(String name) { JButton btn = null. String[] buttonNames = buf. } Die einzelnen Schalter werden von der Hilfsmethode createToolBarButton() erzeugt.println("Symbolleistenressource nicht verfügbar!"). System.createHorizontalStrut(5)). } } return toolBar.add(btn). Alle Informationen zur Konfiguration des Schalters werden der Ressourcendatei entnommen.add(Box.setMargin(new Insets(1. try { // Ressourcenstring für Symbolleiste abfragen und in // String-Array mit Namen der Schaltflächen aufsplitten String buf = resources. btn. sie übernimmt den Ressourcennamen des Menübefehls und erzeugt zu diesem eine passende JButton-Instanz.equals("-")) toolBar.getString(name + "Symbol"). d.getString("toolBar"). Zum Schluss wird die JButton-Instanz für später in die Hashtabelle toolBarButtons eingetragen und als Ergebniswert zurückgeliefert. URL url = this. . try { // Schalter mit Bild erzeugen String image = resources. btn. } } } catch (MissingResourceException mre) { System.setFocusable(false). else { JButton btn = createToolBarButton(s). if (btn != null) toolBar.h.err.368 >> Menüleiste (Symbolleiste) aus Ressourcendatei aufbauen /** * Liefert eine Referenz auf die Symbolleiste zurück * Beim ersten Aufruf wird die Symbolleiste erzeugt */ protected JToolBar getToolBar() { if ( toolBar == null) { toolBar = new JToolBar(). // Schalter für Symbolleiste erzeugen. btn = new JButton(new ImageIcon(url)).1.getResource(image).. GUI // Symbolleiste erzeugen for (String s : buttonNames) { if (s.exit(1).

} catch (MissingResourceException mre) { // tooltip bleibt null } if (tooltip != null) btn. } catch (MissingResourceException mre) { System.get("FileNew").exit(1). } GUI Im Programm... tb.getToolBarButtons(). .>> GUI // ToolTip String tooltip = null. // Ereignisbehandlung für Symbolleistenschalter HashMap<String.err.println("Symbolleistenressource nicht verfügbar!"). try { tooltip = resources. genauer gesagt im Konstruktor der Fensterklasse. tb = toolBarButtons.addActionListener(fileOpenAction). tb. // Symbolleiste aufbauen und am oberen Rand des Fensters einfügen toolBar = mf.get("FileOpen")..addActionListener(fileNewAction). .NORTH). BorderLayout. btn). JButton> toolBarButtons = mf.getString(name + "Tooltip").add(toolBar.setToolTipText(tooltip). System. JButton tb.. wird die Symbolleiste durch Aufruf der MenuFactory-Methode getToolBar() erzeugt und in den NORTH-Abschnitt des Border-Layouts eingefügt: public ProgramFrame() { .. getContentPane(). tb = toolBarButtons. } return btn.getToolBar().. 369 // Schalter in Feld toolBarButtons für späteren Zugriff // abspeichern toolBarButtons.put(name. Danach müssen die einzelnen Schalter nur noch mit den zugehörigen ActionListener-Objekten verbunden werden: .

Symbolleistenschalter durch Instanzen der Klassen JButton oder JToggleButton repräsentiert. Wenn ein Befehl sowohl als Menübefehl in der Menüleiste wie auch als Schalter in der Symbolleiste (und womöglich auch noch als Befehl in einem Kontextmenü) vertreten ist. indem Sie einfach die Aktion aktiveren/deaktivieren: // 1. Action-Klasse zur Behandlung des Datei/Neu-Befehls class FileNewAction extends AbstractAction { public void actionPerformed(ActionEvent e) { // Tue etwas } } // 2. // btnFileNew sei JButton-Objekt // Irgendwo im Code .setAction(a).und Symbolleiste 138 Befehle aus Menü und Symbolleiste zur Laufzeit aktivieren und deaktivieren Menübefehle werden in Java durch Instanzen der Klasse JMenuItem. mit der die Menübefehle und Schalter zur Laufzeit aktiviert (Übergabe von true) oder deaktiviert (Übergabe von false) werden können. // miFileNew sei JMenuItem-Objekt btnFileNew. miFileNew. die mit ein und derselben Aktion verbunden sind. Sie können diese Arbeit aber auch delegieren. Aktionen sind in diesem Sinne Instanzen von Klassen.370 >> Befehle aus Menü und Symbolleiste zur Laufzeit aktivieren und deaktivieren GUI Abbildung 71: Programm mit automatisch generierter Menü. dass alle Vorkommen des Befehls zusammen aktiviert oder deaktiviert werden.setAction(a). Action mit Menübefehl und Symbollschalter verbinden Action a = new FileNewAction(). In diesem Fall können Sie alle Menübefehle und Schalter. die das Interface Action definieren oder von der Klasse AbstractAction abgeleitet sind. zusammen aktivieren oder deaktivieren. Alle drei Klassen sind von AbstractButton abgeleitet und erben von dieser Klasse die Methode setEnabled(). müssen Sie selbstredend darauf achten. indem Sie für die Ereignisbehandlung Aktionen definieren.

müssen Sie alle GUI-Komponenten deaktivieren. einen Mnemonic-Buchstaben.und Symbolleiste mit Aktionen synchronisieren In Rezept 137 wurde Ihnen eine Möglichkeit vorgestellt. Kann die Aktion auf mehreren Wegen ausgelöst werden. die der Anwender auslösen kann. Menü und Symbolleiste automatisch aus einer Ressourcendatei erstellt. Dieser Ansatz ist effektiv und schnell zu implementieren. der Symbolleiste sowie drei Hashtabellen für die Menübefehle. Wollen Sie die Aktion mit einem anderen Tastaturkürzel verbinden. Die Klasse MenuFactory Mit Hilfe der Klasse MenuFactory lassen sich Menü. Dort finden Sie auch nähere Erläuterungen zum Aufbau der Ressourcendatei und zur Namensgebung für die Ressourcen. Wenn Sie also die Aktion deaktivieren wollen.>> GUI // 3. neues Symbol etc. einen Tooltip-Text und eine ausführlichere Beschreibung speichern. das Aktionen.und Symbolleisten aus Ressourcendateien einlesen. können mit einer Action verbunden werden (Methode setAction()). die das ActionListener-Interface implementieren. nur ein Objekt der betreffenden ActionListenerKlasse erzeugt werden. wie Sie Menü. Symbolleistenschalter und Actions. Alle GUI-Komponenten. Werden mehrere GUI-Komponenten mit ein und derselben Action verbunden. beispielsweise über Menübefehl und Symbolschalter. 139 Menü. Aktion für Datei/Neu und alle zugeordneten GUI-Komponenten deaktivieren a. ein Tastaturkürzel. Das folgende Rezept baut Menü. Die Klasse definiert private Felder zum Abspeichern der Ressourcendatei. spiegeln sich Änderungen an der Action (Deaktivierung. wird das zugehörige ActionListener-Objekt einfach bei allen auslösenden GUI-Komponenten als ActionListener registriert. die sowohl über das Menü als auch die Symbolleiste ausgeführt werden könnten. Actions besitzen eine actionPerformed()-Implementierung und können unter anderem ein Symbol.) in allen verbundenen Komponenten wider. die von AbstractAction abgeleitet sind oder direkt das Interface Action implementieren. Actions sind Objekte von Klassen. Wenn Sie mehrere GUI-Komponenten mit ein.setEnabled(false). Unterschiedliche Instanzen der Klasse können unterschiedliche Menüsysteme repräsentieren.und demselben ActionListener-Objekt verbinden. deren Klassen auf AbstractButton zurückgehen. Für die Ereignisbehandlung haben wir dort eigene Klassen definiert. der Menüleiste. Die GUI-Komponenten sind jedoch nicht synchronisiert.und Symbolleiste aus den Daten einer Ressourcendatei auf und verbindet die Menübefehle und Symbolleistenschalter mit Action-Objekten. um den Programmierer von lästiger Verwaltungsarbeit zu befreien. Die Ressourcendatei Die Ressourcendatei zu diesem Rezept ist identisch mit der Ressourcendatei aus Rezept 137. Die Hashtabellen für die Menübefehle und Symbolleistenschalter baut MenuFactory selbst auf. 371 Ein Beispiel hierfür finden Sie im nachfolgenden Rezept. GUI . spart dies Speicherplatz und der Ereignisbehandlungscode steht nur einmal im Quelltext.und Symbolleiste halbautomatisch aus den Daten einer Ressourcendatei erstellen können. Dies leistet das Konzept der Actions. wäre eine derartige automatische Synchronisierung aber wünschenswert. müssen Sie die Tastaturkürzel für alle GUI-Komponenten ändern. Außerdem muss für jede Aktion. Gerade für Aktionen.

getBundle(res. Die Klasse MenuFactory geht davon aus.println("Ressourcendatei nicht verfuegbar!"). } catch (MissingResourceException mre) { System. Action> actions) { this. Titel und Mnemonic-Buchstabe des Menüs werden . loc). try { resources = ResourceBundle. Dazu greift die Methode auf die menuBar-Ressource zu.und Symbolleiste mit Aktionen synchronisieren die Hashtabelle der Actions wird vom aufrufenden Programm entgegengenommen. Die Klasse MenuFactory konfiguriert die Action-Objekte dann gemäß den Menübefehlsdaten aus der Ressourcendatei. System.) public MenuFactory(String res. (Die Definition der Action-Klassen für die Menübefehle kann die Klasse MenuFactory dem Hauptfenster wegen der Implementierung der actionPerformed()-Methode nicht abnehmen. private HashMap<String.. H in we is . die zu verwendende Lokale und eine HashMap mit den Action-Objekten.exit(1). Die HashMap nimmt MenuFactory entgegen und konfiguriert dann die Action-Objekte gemäß den Menübefehlinformationen aus der Ressourcendatei. JMenuItem> menuItems = new HashMap<String. dass das Hauptfenster die entsprechenden Klassen definiert und für jeden Menübefehl ein Action-Objekt instanziert und unter dem Namen der Menübefehlsressource in eine HashMap einfügt. Existiert noch gar keine Menüleiste. private JToolBar toolBar = null. JMenuItem>().372 >> Menü. private HashMap<String.actions = actions. in einer HashMap speichern und an den Konstruktor von MenuFactory übergeben. Die Methode createMenu() übernimmt als Argument den Namen einer Menüressource und liefert als Ergebnis das fertige Menü zurück. HashMap<String. Locale loc. Die Action-Objekte muss der Aufrufer (sprich die Hauptfensterklasse) erzeugen. Stattdessen erwartet MenuFactory. public class MenuFactory { private ResourceBundle resources. JButton>(). private JMenuBar menuBar = null. Symbolleistenschalter und Actions werden in den Hashtabellen unter den Ressourcennamen der Menübefehle (Schlüssel) gespeichert. GUI } Bei der Instanzierung der Klasse übergeben Sie dem Konstruktor den Namen der Ressourcendatei (gegebenenfalls einschließlich relativem Pfad). Menübefehle. dass es zu jedem Menübefehl ein Action-Objekt gibt. JButton> toolBarButtons = new HashMap<String. Action> actions = null.err. } Die für den Benutzer wichtigste Methode ist getMenuBar(). private HashMap<String. Die Menüleiste mit den Menübefehlen baut MenuFactory selbstständig aus den Daten der Ressourcendatei auf. wird sie automatisch aufgebaut.. die eine Referenz auf die JMenuBarMenüleiste zurückliefert. zerlegt deren Wert in die Namen der einzelnen Menüs und lässt diese von der protected-Methode createMenu() erzeugen.

} Die protected-Methode configAction() übernimmt den Namen einer Menübefehlsressource und konfiguriert die zugehörige Action gemäß den Angaben. GUI .>> GUI 373 der Ressourcendatei entnommen. muss also in der Ressourcendatei definiert sein. ansonsten wird der Ressourcenname des Befehls an die protected-Methode createMenuItem() weitergereicht. ausgeschaltet. wobei Letzterer nicht unbedingt definiert sein muss.setIcon(null). label). // keine Symbole für Menübefehle menuItems. a.. Die Methode createMenuItem() schließlich erzeugt die einzelnen Menübefehle als Instanzen von JMenuItem. mi. /** * Action konfigurieren. die zu dem Menübefehl in der Ressourcendatei gespeichert sind. die die zu dem Menübefehl gehörende Action gemäß den Daten aus der Ressourcendatei konfiguriert. Welche Menübefehle das Menü enthalten soll.get(name).h. a = actions. d. wird von createMenuItem() und * createToolBarButton() aufgerufen */ protected Action configAction(String name) { Action a = null. Dazu ruft sie die Methode configAction() auf. // Menübefehl mit Aktion verbinden und für späteren Zugriff // in Feld menuItems abspeichern mi. // Mnemonic setzen String mnemo = null. Action a = configAction(name). Der Titel (xxxLabel) ist obligatorisch.putValue(Action. // keine Tooltipps für Menübefehle mi.NAME. Für einen Bindestrich wird eine Trennlinie in das Menü eingefügt.setAction(a). die für den Menübefehl nicht benötigt werden (Tooltipps und Symbole). Anschließend wird der Menübefehl selbst angepasst. entnimmt die Methode dem Wert der Menüressource. mi).put(name. Die restlichen Angaben sind optional. try { // Action-Objekt zu Menübefehlsnamen beschaffen String label = resources.getString(name + "Label"). Die fertig konfigurierte Action wird dann durch Aufruf von setAction() mit dem Menübefehl verbunden. es werden durch Aufruf der entsprechenden JMenuItem-Methoden diejenigen Action-Elemente. return mi. public JMenuBar getMenuBar() { // wie in Rezept 137 } protected JMenu createMenu(String name) { // wie in Rezept 137 } // Menübefehl erzeugen. wird von createMenu() aufgerufen protected JMenuItem createMenuItem(String name) { JMenuItem mi = new JMenuItem().setToolTipText(null).

} catch (MissingResourceException mre) { // tooltip bleibt null } if (tooltip != null) a. new ImageIcon(url)).charAt(accel. try { image = resources.getResource(image).SMALL_ICON. a.length()-1).getString(name + "Symbol").codePointAt(0) ). // Tastaturkürzel setzen String accel = null. if (url != null) { a. mnemo. try { description = resources.MNEMONIC_KEY.putValue(Action. } catch (MissingResourceException mre) { // accel bleibt null } if (accel != null) { KeyStroke ks = KeyStroke.) setzen String description = null.putValue(Action. try { tooltip = resources.putValue(Action.getString(name + "Tooltip"). // Beschreibung (für Statusleiste etc.putValue(Action.getString(name + "Accelerator"). tooltip). ks). Event. try { accel = resources. } // Bild setzen String image = null.getKeyStroke( (int) accel. } } // ToolTip setzen String tooltip = null. } catch (MissingResourceException mre) { // description bleibt null } if (description != null) GUI .getString(name + "Description").SHORT_DESCRIPTION. } catch (MissingResourceException mre) { // mnemo bleibt null } if (mnemo != null) a.CTRL_MASK).und Symbolleiste mit Aktionen synchronisieren try { mnemo = resources. } catch (MissingResourceException mre) { // image bleibt null } if (image != null) { URL url = this.getClass().ACCELERATOR_KEY.374 >> Menü.getString(name + "Mnemonic").

die Referenzen auf die Hashtabellen für die Menübefehle und die Symbolleistenschalter zurückliefern. Sie definiert für jeden Menübefehl eine von AbstractAction abgeleitete Action-Klasse. btn = new JButton(a). gegebenenfalls Felder für die Menüleiste und die Symbolleiste sowie eine HashMap-Collection für die Action-Objekte: public class ProgramFrame extends JFrame { private MenuFactory mf.exit(1).>> GUI a. die actionPerformed() implementiert. } Als Pendant zu den Methoden zur Erzeugung der Menüleiste gibt es Methoden zum Aufbau einer Symbolleiste. JButton> getToolBarButtons() { return toolBarButtons. GUI . Sie definiert ein private Feld für das MenuFactory-Objekt. 375 } catch (MissingResourceException mre) { System. return btn. 2. geht wie folgt vor: 1. } return a.err.LONG_DESCRIPTION.put(name. HashMap<String. Action a = configAction(name). die mit Hilfe von MenuFactory ihre Menüleiste aufbauen möchte. protected JToolBar getToolBar() { // wie in Rezept 137 } protected JButton createToolBarButton(String name) { JButton btn = null. btn. } Zu guter Letzt definiert die Klasse zwei Methoden. } public HashMap<String.println("Menüressource nicht verfügbar!"). description). } } Verwendung in einem Programm Eine Frame-Klasse. toolBarButtons. btn).putValue(Action. System. public HashMap<String. JMenuItem> getMenuItems() { return menuItems.setText(null). Action> actions. private JMenuBar menuBar.

// Menü aufbauen und als Hauptmenü des Fensters einrichten mf = new MenuFactory("resources/Program".put("FileOpen". new EditCutAction()).und Symbolleiste mit Aktionen synchronisieren Hierfür gibt es eine Vielzahl von Möglichkeiten (siehe Rezept 119).setEnabled(true). } } class FileOpenAction extends AbstractAction { public void actionPerformed(ActionEvent e) { System.out.put("EditPaste".put("EditCut"....println(" Datei / Neu"). actions. wobei sie den Pfad zur Ressourcendatei. public ProgramFrame() { .setEnabled(true). actions. new FileNewAction()). actions.getDefault().put("InfoInfo"..put("FileSaveAs". Als Schlüssel zu den Actions dient jeweils der Name der Menübefehlsressource..setEnabled(true). Action>().. actions).put("FileQuit"..get("FileSaveAs"). actions..376 >> Menü. public ProgramFrame() { . 4. actions.. Sie erzeugt im Konstruktor eine Instanz von MenuFactory.setEnabled(true). actions. new EditCopyAction()). und legt im Konstruktor eine HashMap mit den gewünschten Action-Objekten an.out.get("FileSave"). new FileSaveAction()). actions. } } GUI . actions. Sie ruft die getMenuBar(). actions. actions. actions.. new FileOpenAction()).get("FileSaveAs").put("FileNew". actions. new InfoInfoAction()). public class ProgramFrame extends JFrame { . 3. new FileSaveAsAction()). ...put("FileSave".get("FileSave").. . actions. // Action-Objekte erzeugen und in Collection // actions speichern actions = new HashMap<String. // innere Action-Klassen für Menübefehle class FileNewAction extends AbstractAction { public void actionPerformed(ActionEvent e) { System.und Symbolleiste aufbauen zu lassen. um Menü. die zu verwendende Lokale und die actions-Collection übergibt. Die Fensterklasse zu diesem Rezept definiert für jeden Befehl eine eigene ActionListener-Klasse . Die von getMenuBar() zurückgelieferte Refe- . new FileQuitAction()). new EditPasteAction()).println(" Datei / Oeffnen"). Locale.und getToolBar()-Methoden des MenuFactory-Objekts auf.put("EditCopy"..

. . BorderLayout.. Wer dennoch nicht auf seine Statusleiste verzichten möchte. setJMenuBar(menuBar).get("FileSave").add(toolBar. die mit den Actions »FileSave« und »FileSaveAs« verbunden sind.>> GUI 377 renz übergibt sie an setJMenuBar()...getToolBar(). muss selbst Hand anlegen. actions. sobald Sie einen der Befehle Neu oder Öffnen ausführen.. getContentPane(). public ProgramFrame() { .setEnabled(false). ein JPanel mit passenden Feldern (in der Regel JLabel-Instanzen) ausstatten und in den SOUTH-Bereich eines Rahmenfensters mit Borderlayout einfügen. .. . Die von getToolBar() zurückgelieferte Referenz fügt sie in den NORTH-Bereich des Border-Layouts ein. sind deaktiviert. Um die Synchronisierung von GUI-Komponenten via Actions zu demonstrieren. toolBar = mf.NORTH).. menuBar = mf. Sie werden aktiviert. müssen sowohl die entsprechenden Menübefehle als auch der Speicher-Schalter deaktiviert sein! Abbildung 72: Anfangszustand des Programms: Die GUI-Komponenten.get("FileSaveAs").. 140 Statusleiste einrichten Es ist ein offenes Geheimnis: Die Java-API kennt keine eigene Klasse für Statusleisten. deaktiviert das Hauptfenster dieses Rezepts anfangs die Actions FileSave und FileSaveAs. actions. . GUI Wenn Sie das Programm starten.getMenuBar().setEnabled(false).

LEFT)).createEtchedBorder()). setDefaultCloseOperation(JFrame. setBackground()-Aufruf) oder durch Auswahl eines Rahmens (siehe oben. // Felder einfügen sb_textfield = new JLabel().*.setBorder(BorderFactory. public class Statusleiste_Simple extends JFrame { private JPanel sb.event.LIGHT_GRAY).*.setLayout(new FlowLayout(FlowLayout. GUI sb_textfield.awt. sb. import javax.EXIT_ON_CLOSE). Box. BorderLayout. . sb.java – Beispiel für die Implementierung einer einfachen Statusleiste Das Erscheinungsbild einer so konstruierten Statusleiste lässt sich auf vielerlei Weise anpassen: beispielsweise durch Einstellung der Hintergrundfarbe (siehe oben.setText("Dies ist die Statusleiste").. sb.*. Abstände und Position der Felder können unter anderem durch Trennstriche (Instanzen von JSeparator) und Abstandshalter (Box. } Listing 174: Aus Statusleiste_Simple.378 >> Statusleiste einrichten import java. Die Klasse StatusBar Die nachfolgend definierte Klasse StatusBar kann Statusleisten aus einer beliebigen Zahl von JLabel-Feldern erzeugen.createHorizontalGlue() zum »Aufsaugen« beliebigen Freiraums) festgelegt werden.. sb. // JPanel als Statusleiste konfigurieren sb = new JPanel(). public Statusleiste_Simple() { // Hauptfenster konfigurieren setTitle("Swing-Grundgerüst").SOUTH).createHorizontalStrut() für Abstände fester Breite bzw.awt. import java. Für die einzelnen Felder der Statusleiste können entsprechende GUI-Komponenten direkt oder wiederum eingebettet in untergeordnete JPanel-Instanzen eingefügt werden.add(sb. private JLabel sb_textfield.swing. createEtchedBorder()-Aufruf).setBackground(Color. } . // JPanel als Statuszeile in Fenster einfügen getContentPane().add(sb_textfield).

LOWERED). import import import import java.5. die einen dekorativen Rahmen (SoftBevelBorder) mit einfachen Rändern unterschiedlicher Bereite (EmptyBorder) verbindet: setBorder(new CompoundBorder( new SoftBevelBorder(SoftBevelBorder.ArrayList.*.swing.VERTICAL).5))). die für den Fall.createHorizontalStrut(5)). Gibt es mehrere Felder. werden diese durch zwei 5 Pixel breite Abstandshalter und einen dazwischen geschalteten vertikalen Trennstrich getrennt.swing. den restlichen Raum einnimmt. dass das Fenster breiter ist als die Maximalgröße des Felds.util.border. /** * Klasse für Statusleisten */ Listing 175: StatusBar.createHorizontalGlue()). new EmptyBorder(1. die ansonsten nur noch eine einzige Methode namens getField(int index) besitzt.awt.createHorizontalStrut(5)).200)). add(Box. Erzeugt werden die Statusleisten von den Konstruktoren der Klasse.*.setMaximumSize(new Dimension(2. sep. d. add(sep). javax.>> GUI 379 Abbildung 73: GUI-Programm mit Statusleiste Das Design der von StatusBar erzeugten Statusleisten ist fix.*. javax. im Code der Klasse festgelegt (kann aber natürlich vom Programmierer geändert werden): Der Rahmen besteht aus einer CompoundBorder-Instanz.0.h. die eine Referenz auf das index-te Feld in der Statusleiste zurückliefert. JSeparator sep = new JSeparator(SwingConstants. java. add(Box. add(Box.java GUI . Auf das erste Feld folgt stets eine Glue-Komponente.

setBorder(new CompoundBorder( new SoftBevelBorder(SoftBevelBorder.add(lb).) GUI . setBorder(new CompoundBorder( new SoftBevelBorder(SoftBevelBorder. setBorder(new CompoundBorder( new SoftBevelBorder(SoftBevelBorder.createHorizontalGlue()).LOWERED).LOWERED).0.5))). lb.. new EmptyBorder(1.X_AXIS)).setPreferredSize(new Dimension(width.5. lb. labels) { setLayout(new BoxLayout(this.5))). fields. add(Box. 16)).setPreferredSize(new Dimension(400. JLabel lb = new JLabel(" ").java (Forts. } /** * erzeugt eine Statusleiste mit einem einzigen Textfeld.X_AXIS)).5. 16)). BoxLayout. 16)). /** * erzeugt eine Statusleiste mit einem einzigen Textfeld.0..X_AXIS)). 16)). lb. lb. BoxLayout.add(lb).setMinimumSize(new Dimension(width. fields. JLabel lb = new JLabel(" "). } /** * erzeugt eine Statusleiste mit den übergebenen JLabel-Komponenten * als Feldern */ public StatusBar(JLabel. das * maximal 400 Pixel breit wird */ public StatusBar() { setLayout(new BoxLayout(this.5.LOWERED).5))).380 >> Statusleiste einrichten class StatusBar extends JPanel { private ArrayList<JLabel> fields = new ArrayList<JLabel>(). dessen * bevorzugte und maximale Breite als Argument übergeben wird */ public StatusBar(int width) { setLayout(new BoxLayout(this. BoxLayout. new EmptyBorder(1. add(lb). Listing 175: StatusBar.createHorizontalGlue()). new EmptyBorder(1. add(Box.setMinimumSize(new Dimension(400. add(lb).0.

setPreferredSize(new Dimension(400.length. field1. getContentPane().setText("Dies ist die Statusleiste"). ++i) { fields. add(sep). add(labels[i]). i < labels. sep.setMaximumSize(new Dimension(2. Wenn Sie mehrere Textfelder in die Statusleiste integrieren möchten. } } public JLabel getField(int index) { if (index >= 0 && index < fields.SOUTH).20)).>> GUI 381 for (int i = 0.) Um eine einfache Statusleiste mit einem einzigen Feld einzurichten. else throw new IllegalArgumentException(). } } Listing 175: StatusBar. müssen Sie die einzelnen Textfelder als JLabel-Instanzen vorab erzeugen.java (Forts. JSeparator sep = new JSeparator(SwingConstants.java GUI . BorderLayout. müssen Sie lediglich den ersten oder zweiten Konstruktor aufrufen und die erzeugte Statusleiste in den SOUTH-Bereich des Rahmenfensters einfügen: statusBar = new StatusBar(). die bevorzugte und die minimale Größe festlegen und dann als Auflistung oder Array an den dritten Konstruktor übergeben: // Felder für Statusleiste erzeugen JLabel field1 = new JLabel(""). Durch Aufruf von getField(0) können Sie auf das Textfeld der Statusleiste zugreifen und einen Text anzeigen.get(index). add(Box. field1.createHorizontalStrut(5)).setMinimumSize(new Dimension(200.200)).getField(0).VERTICAL).createHorizontalStrut(5)). add(Box.add(statusBar.add(labels[i]). Listing 176: Aus ProgramFrame. statusBar.20)).size()) return fields.createHorizontalGlue()). if (i == 0) add(Box.

get(c). BorderLayout. // Gibt es einen Hinweistext.SOUTH). Die entsprechenden Ereignisbehandlungsmethoden des MouseListener-Interfaces lauten mouseEntered() und mouseExited(). // Statusleiste einrichten statusBar = new StatusBar(field1. über der // der Mauszeiger steht Component c = (Component) e. field3.java (Forts.20)). field2.setText(hint). Listing 176: Aus ProgramFrame. Umgekehrt ist die Textbeschreibung zu löschen. field2. wenn die Maus wieder von der Komponente wegbewegt wird.setText(" "). } // Maus wird von registrierter GUI-Komponente wegbewegt // -> Hinweistest ausblenden public void mouseExited(MouseEvent e) { statusBarField.setMinimumSize(new Dimension(20.20)).setPreferredSize(new Dimension(20. 141 Hinweistexte in Statusleiste Um Hinweistexte zu Menübefehlen und Symbolleistenschaltern in der Statusleiste anzuzeigen. field2.20)).20)). // Maus wird über registrierte GUI-Komponente bewegt // -> Hinweistest anzeigen public void mouseEntered(MouseEvent e) { // Hinweistext der Komponente abfragen. JLabel field4 = new JLabel("Z"). ist dieser in der Statusleiste anzuzeigen.20)). field3.20)). müssen Sie die Maus überwachen. Wird die Maus über die GUI-Komponente eines Menübefehls oder eines Symbolleistenschalters bewegt.setPreferredSize(new Dimension(20. } . String hint = hintMap.) GUI Das Programm zu diesem Rezept erweitert das Programm aus Rezept 139 um eine Statusleiste.382 >> Hinweistexte in Statusleiste JLabel field2 = new JLabel("X"). JLabel field3 = new JLabel("Y").getSource().setMinimumSize(new Dimension(20.getField(0). field4). zu dem es eine Textbeschreibung für die Statusleiste gibt.setPreferredSize(new Dimension(20. field3. getContentPane(). field4.add(statusBar.setMinimumSize(new Dimension(20. field4. statusBar.setText("Dies ist die Statusleiste"). schreibe diese in die // das Feld der Statusleiste if (hint != null) statusBarField.

>> GUI 383 Die Implementierung der mouseEntered()-Methode setzt voraus. java. this.*. String>(). statusBarField auf das JLabel-Feld der Statusleiste weist. String> hintMap.java GUI . Natürlich könnte man hintMap und statusBarField als Felder der Rahmenfensterklasse definieren und auch die Methoden des MouseListener-Interfaces in der Fensterklasse implementieren. } // Methode zum Registrieren von GUI-Komponenten und der zugehörigen // Beschreibung public void addComponentHint(Component c. in der die Hinweistexte für die GUI-Komponenten abge- legt sind. text).put(c. } // Maus wird über registrierte GUI-Komponente bewegt // -> Hinweistest anzeigen public void mouseEntered(MouseEvent e) { // wie oben } Listing 177: StatusBarHintManager.swing.*. mit den Komponentenreferenzen als Schlüssel. // Container zum Sammeln der Hinweise // Key = Komponente.awt. // Konstruktor public StatusBarHintManager(JLabel statusBarField) { hintMap = new WeakHashMap<Component. die die Verwaltung der Textbeschreibungen übernimmt. in welches der Hinweistext aus- gegeben werden soll. javax.*.addMouseListener(this).statusBarField = statusBarField. java. /** * Klasse zur Verwaltung von Hinweistexten für die Statusleiste * * @author Dirk Louis */ public class StatusBarHintManager extends MouseAdapter { // Statusleistenfeld für Anzeige private JLabel statusBarField. dass hintMap eine Map-Collection ist.util. import import import import java.event. Value = Text private WeakHashMap<Component. Besser wieder verwendbar ist aber die Definition einer eigenen Manager-Klasse.awt.WeakHashMap. String text) { hintMap. c.

mit der Komponenten inklusive Text registriert werden können. nach Einrichtung der Statusleiste. für die in der Statusleiste Hinweistexte angezeigt werden sollen.) GUI Neben den Methoden zur Mausüberwachung und einem Konstruktor.getMenuItems(). in denen unter dem Schlüssel LONG_DESCRIPTION auch Beschreibungen für die einzelnen Aktionen gespeichert sind. definiert die Klasse nur noch eine einzige Methode: addComponentHint(). in der Fensterklasse eine Instanzvariable für den StatusBarHintManager definieren: private StatusBarHintManager sbHintManager. Hinweistext abfragen if (a != null) { description = (String) a.getField(0)). 2.getToolBarButtons(). Listing 178: Aus ProgramFrame. // StatusBarHintManager erzeugen sbHintManager = new StatusBarHintManager(statusBar.384 >> Hinweistexte in Statusleiste // Maus wird von registrierter GUI-Komponente wegbewegt // -> Hinweistest ausblenden public void mouseExited(MouseEvent e) { // wie oben } } Listing 177: StatusBarHintManager. Dort wurden Menü. mf. gehen Sie so vor. Wie Schritt 2 im Detail zu implementieren ist. der die Referenz auf das Statusleistenfeld für die Anzeige der Hinweistexte übernimmt.values()) { // Action-Objekt zu Komponente besorgen a = m. // Collection der Menübefehl-Komponenten durchlaufen for (JMenuItem m : mf.LONG_DESCRIPTION). Referenzen auf die GUI-Komponenten für die Menübefehle und Symbolleistenschalter können mittels mf.values() bzw.und Symbolleiste weitgehend automatisch mit Hilfe der Klasse MenuFactory aufgebaut. // Wenn Action-Objekt vorhanden.getValue(Action. dass Sie 1. Den einzelnen Komponenten sind Action-Objekte zugeordnet. Action a. hängt von dem jeweiligen Programm ab.java (Forts.getAction(). eine Instanz der Klasse StatusBarHintManager erzeugen und die GUI-Komponenten registrieren. Das Programm zu diesem Rezept stellt beispielsweise eine Erweiterung des Programms aus Rezept 139 dar.values() beschafft werden (wobei mf eine Instanz von MenuFactory ist). String description = null. im Konstruktor der Klasse.getMenuItems().java – Komponenten mit Hinweistexten bei StatusBarHintManager registrieren . Um Hinweistexte zu einzelnen GUI-Komponenten eines Programms in die Statusleiste einzublenden.

APPROVE_OPTION == openDialog. öffnen if(f..getAction().getToolBarButtons(). ist nicht sonderlich schwer: JFileChooser openDialog = new JFileChooser().LONG_DESCRIPTION). // Prüfen. description).getValue(Action.>> GUI 385 // Wenn Hinweistext zu Komponenten verfügbar.getSelectedFile().addComponentHint(b.java – Komponenten mit Hinweistexten bei StatusBarHintManager registrieren (Forts.. if (JFileChooser. } } Listing 178: Aus ProgramFrame. description). ob File-Objekt wirklich eine Datei ist // Wenn ja und wenn lesbar.isFile() && f. GUI .canRead()) { .) Abbildung 74: Programm mit Hinweistexten in der Statusleiste 142 Dateien mit Datei-Dialog (inklusive Filter) öffnen Einen Datei-Dialog anzuzeigen und sich den vom Anwender ausgewählten Dateinamen zurückliefern zu lassen. } } // Collection der Symbolleistenschalter durchlaufen for (JButton b : mf. if (description != null) sbHintManager. if (a != null) { description = (String) a.showOpenDialog(this)) { // Datei abfragen File f = openDialog.addComponentHint(m. Komponente // samt Text registrieren if (description != null) sbHintManager.values()) { a = b.

. übergeben. es kann auch eine Datei aus dem Verzeichnis repräsentieren.isFile() && f. Speichern Sie einfach nach jedem erfolgreichen Laden einer Datei das File-Objekt in einem Feld der Fensterklasse und setzen Sie vor jedem Dialogaufruf das aktuelle Verzeichnis: public class ProgramFrame extends JFrame { private File lastDir = null. Soll der Dialog anfangs ein anderes Verzeichnis anzeigen. Diesen Umstand können Sie sich zu Nutze machen.setCurrentDirectory(lastDir). aus dem die zuletzt geöffnete Datei ausgewählt wurde. if(f. } } Listing 179: Aus ProgramFrame.386 >> Dateien mit Datei-Dialog (inklusive Filter) öffnen Oft sind mit dem Öffnen einer Datei aber noch weitere Aspekte verbunden: Wie erreicht man.. Dabei muss das File-Objekt nicht unbedingt das Verzeichnis selbst sein.java . if (JFileChooser.. // Aktuelles Verzeichnis sichern lastDir = f.println("Fehler beim Öffnen").getSelectedFile(). müssen Sie setCurrentDirectory() aufrufen und eine File-Instanz. dass der Öffnen-Dialog sich bei erneutem Aufruf an das Verzeichnis erinnert.err. damit im Öffnen-Dialog nur Dateien mit bestimmten Extensionen angezeigt werden? Welche Aufgaben sollte eine fileOpen-Methode neben dem reinen Öffnen noch erledigen? GUI Startverzeichnis des Öffnen-Dialogs einstellen Per Voreinstellung lädt JFileChooser das Heimverzeichnis des Anwenders in den Öffnen-Dialog. // zuletzt benutztes Verzeichnis . } catch (IOException e) { System.APPROVE_OPTION == openDialog. die das gewünschte Verzeichnis repräsentiert.. protected void fileOpen() { // Zuletzt verwendetes Verzeichnis auswählen openDialog.canRead()) try { // Text aus Datei einlesen . aus welchem der Anwender das letzte Mal die zu öffnende Datei ausgewählt hat? Wie installiert man einen Dateifilter.showOpenDialog(this)) { // Datei abfragen File f = openDialog. wenn Sie bei Aufruf des Dialogs das Verzeichnis anzeigen wollen.

Filter Wenn Sie nur Dateien mit speziellen Dateiextensionen im Öffnen-Dialog anzeigen wollen. Nur File-Objekte. Listing 180: ConfigurableFileFilter. für die true zurück* geliefert wird. (JFileChooser geht die Dateien im aktuellen Verzeichnis durch und übergibt sie zur Überprüfung an die accept()-Methoden der registrierten FileFilter. this. Der Benutzer der Klasse braucht dem Konstruktor einfach nur die Textbeschreibung und die Liste der Dateiextensionen (als Auflistung oder als Array) zu übergeben – fertig! import javax. werden im Datei-Dialog angezeigt.io. String. import java. müssen Sie zu diesem Zweck einen FileFilter schreiben. startet der Dialog mit dem Heimverzeichnis des Anwenders.extensions = ext.swing. } /** * Prüfen. private String[] extensions.filechooser.>> GUI 387 Achten Sie darauf.description = desc.) GUI zurückliefert. ext) { this.filechooser.FileFilter. /** * Generischer Dateifilter */ class ConfigurableFileFilter extends FileFilter { private String description. lastDir anfangs auf null zu setzen. wenn die Datei f angezeigt werden soll. wenn setCurrentDirectory() dann null als Argument überreicht wird. die true zurückliefern soll. /** * Filter mit Beschreibung und Liste von Dateierweiterungen erzeugen */ public ConfigurableFileFilter(String desc. */ public boolean accept(File f) { // Verzeichnisse alle anzeigen if(f..isDirectory() == true) return true.java – ein allgemein verwendbarer File-Filter . String getDescription().File. ob die gegebene Datei zu einer der registrierten Datei* erweiterungen gehört. Beim ersten Aufruf des Öffnen-Dialogs.swing.FileFilter abgeleitet und überschreiben die abstrakten Methoden boolean accept(File f).. FileFilter werden von der Klasse javax. die die Dateibeschreibung (Text in Datentyp-Listenfeld des Dialogs) Die folgende Klasse ConfigurableFileFilter implementiert einen generischen FileFilter.

swing.awt. javax.swing. } return false.*. javax. Listing 181: ProgramFrame.) fileOpen-Methode Wie sollte eine fileOpen-Methode aufgebaut sein? Hier einige Vorschläge:3 Aktion Datei zur internen Auswertung durch Programm laden – x x – – – Dateibetrachter (Öffnen-Befehl) – x x – – x Dateieditor3 (Öffnen. } public String getDescription() { return description.text. Der gesamte Code zum Öffnen von Dateien ist in der fileOpen()-Methode untergebracht.*. java.isFile()) { for( String s : extensions) if(f. Listener für Änderungen in Datei registrieren 5.java – ein allgemein verwendbarer File-Filter (Forts. import import import import import import java.*. javax. Speichern-Befehl deaktivieren 6. die bei Auswahl des Datei/Öffnen-Befehls ausgeführt wird.388 >> Dateien mit Datei-Dialog (inklusive Filter) öffnen else if (f. Dateiname in Fenstertitel einblenden Tabelle 37: Aufbau von fileOpen-Methoden Das Programm zu diesem Rezept entspricht einem einfachen Dateibetrachter.und Speichern-Befehl) x x x x x (optional) x 1.*.*. Prüfen. siehe Rezepte 143 und 146 . } } GUI Listing 180: ConfigurableFileFilter.event.util.java 3. ob es nicht gesicherte Änderungen gibt 2.awt.swing. java.*. Ausgewählte Datei laden 4. Öffnen-Dialog anzeigen 3.event.endsWith(s)) return true.getName().

addActionListener(new ActionListener() public void actionPerformed(ActionEvent e) System.add(miQuit). // Menü aufbauen und als Hauptmenü des Fensters einrichten menuBar = new JMenuBar(). } }). menuBar.setMnemonic(KeyEvent. miOpen. private ConfigurableFileFilter filter. fileMenu.setMnemonic(KeyEvent. private JTextArea textpane.VK_F). miOpen. // Dialog private JFileChooser openDialog.) { { { { . Listing 181: ProgramFrame. public class ProgramFrame extends JFrame { private JMenuBar menuBar. // Textfeld private JScrollPane scrollpane.io. miQuit = new JMenuItem("Beenden"). } }). private JMenuItem miQuit. miQuit. JMenu fileMenu = new JMenu("Datei").java (Forts.>> GUI 389 import java. miOpen = new JMenuItem("Öffnen"). fileMenu. miQuit. private File file = null. // zuletzt benutztes Verzeichnis GUI public ProgramFrame() { // Hauptfenster konfigurieren setTitle(programName).setMnemonic(KeyEvent.add(miOpen).VK_D). setJMenuBar(menuBar). private final String programName = "Programm".VK_B).addActionListener(new ActionListener() public void actionPerformed(ActionEvent e) fileOpen().*.add(fileMenu). private JMenuItem miOpen. // aktuell geöffnete Datei private File lastDir = null.exit(0). fileMenu.

12)). lastDir = f. Listing 181: ProgramFrame. "html").CENTER). textpane.PLAIN. if (JFileChooser. if(f.html)".setLineWrap(true).getSelectedFile(). "txt". textpane.isFile() && f. } /* * Datei öffnen und einlesen */ protected void fileOpen() { // Zuletzt verwendetes Verzeichnis auswählen openDialog. f). GUI setDefaultCloseOperation(JFrame.close().add(scrollpane.setWrapStyleWord(true). . scrollpane = new JScrollPane().showOpenDialog(this)) { // Datei abfragen File f = openDialog.setFont(new Font("SansSerif". in. // Felder und Fenstertitel aktualisieren file = f.read(in.WHITE).canRead()) try { // Text aus Datei einlesen FileReader in = new FileReader(f). textpane. filter = new ConfigurableFileFilter("Textdokumente (. adjustWindowTitle(). null). BorderLayout. Font. // Filter für Datei-Dialog konfigurieren openDialog = new JFileChooser().390 >> Dateien mit Datei-Dialog (inklusive Filter) öffnen // Textfeld einrichten textpane = new JTextArea(). openDialog. textpane.getViewport().add(textpane.) .APPROVE_OPTION == openDialog. textpane. getContentPane().java (Forts.setBackground(Color.addChoosableFileFilter(filter).EXIT_ON_CLOSE). scrollpane.txt.setCurrentDirectory(lastDir).

file. this.) GUI 143 Dateien mit Speichern-Dialog speichern Einen Speichern-Dialog anzuzeigen und die aktuellen Daten in der vom Anwender ausgewählten Datei zu speichern.. if (file == null) title = "Unbenannt". ohne zuvor gespeichert zu haben.getName(). das Textdateien in einer JTextArea-Komponente namens textpane anzeigt und für die Dateiverwaltung – wie viele ." + title.showSaveDialog(this)) { // Datei abfragen file = openDialog. wenn es noch nicht gespeicherte Änderungen gibt? Die folgenden Ausführungen beziehen sich auf ein Programm.err. dass Änderungen am aktuellen Dokument verloren gehen.APPROVE_OPTION == openDialog. // Daten in Datei file speichern try { . } } Listing 181: ProgramFrame. dass der Speichern-Befehl nur aktiviert ist. ist nicht sonderlich schwer: JFileChooser openDialog = new JFileChooser(). Wie verhindert man.getName()). if (JFileChooser. title = programName + " ..und Speichern unter-Menübefehle an. Oft sind mit dem Speichern einer Datei aber noch weitere Aspekte verbunden: Viele Anwendungen bieten Speichern. else title = file. } } } /* * Dateinamen in der Titelleiste des Fensters anzeigen */ private void adjustWindowTitle() { String title.getSelectedFile(). ein neues Dokument anlegt oder öffnet? Wie erreicht man.>> GUI 391 } catch (IOException e) { System.println("Fehler beim Öffnen von " + this.setTitle(title).java (Forts. wenn der Anwender.

write(out). wenn es zu dem aktuellen Dokument keine Datei gibt (beispielsweise weil das Dokument nicht mit dem DATEI/ÖFFNEN-Befehl aus einer Datei geladen. return true. sondern mit DATEI/NEU neu angelegt wurde)? Nun.close(). } catch (IOException e) { System.println("Fehler beim Speichern von " + file. ganz einfach. DATEI/SPEICHERN und DATEI/SPEICHERN UNTER anbietet. in diesem Fall leitet man zur fileSaveAs()-Methode weiter. den Namen abzufragen. Speichern – Speichern unter Der SPEICHERN-Befehl soll das aktuelle Dokument in der zugehörigen Datei speichern. in fileSaveAs() lediglich 1. dann fileSave() aufzurufen 3.err. und gegebenenfalls noch den Fenstertitel zu aktualisieren. return false. 2. Mit anderen Worten: Der DATEI/ÖFFNEN-Befehl muss file das gerade geöffnete File-Objekt zuweisen (siehe Rezept 142). das entweder das File-Objekt zum aktuellen Dokument speichert oder null ist. Da der Speichervorgang selbst bereits in fileSave() implementiert wurde. bietet es sich an. out. } } Listing 182: fileSave-Methode für Speichern-Befehl Wichtig ist. Der SPEICHERN UNTER-Befehl soll das aktuelle Dokument unter einem neuen Namen abspeichern. textpane.392 >> Dateien mit Speichern-Dialog speichern andere Anwendungen auch – die Befehle DATEI/NEU.getName()). . dass file ein Feld vom Typ File ist. Was aber. dann nach fileSaveAs() umleiten if (file == null) { return fileSaveAs(). } try { // Text aus JTextArea textpane in Datei schreiben FileWriter out = new FileWriter(file). /** * Datei speichern */ protected boolean fileSave() { GUI // Text noch nicht mit Datei verbunden. der DATEI/NEU-Befehl muss file auf null setzen (siehe weiter unten). DATEI/ÖFFNEN.

// rückmelden.APPROVE_OPTION == openDialog. welches durch seinen Wert anzeigt. } Listing 183: fileSaveAs-Methode für Speichern unter-Befehl GUI Die Methode adjustWindowTitle() wurde bereits als Teil des Programms aus Rezept 142 vorgestellt.getSelectedFile().setCurrentDirectory(lastDir). besteht immer die Gefahr. ob es nicht gespeicherte Änderungen gibt. die letzten Änderungen im aktuellen Dokument zu speichern. Ein gutes Programm sollte daher überwachen. } return false. ob es nicht gespeicherte Änderungen gibt (true) oder nicht (false): public class ProgramFrame extends JFrame { private boolean dirty = false. Zur Überwachung nicht gespeicherter Änderungen definieren Sie am besten in der Fensterklasse ein boolean-Feld. Vorsicht Öffnen-Befehl! Wenn der Anwender ein neues Dokument anlegt oder ein bereits bestehendes Dokument öffnet. } return false. // gibt es nicht gespeicherte Änderungen? Im nächsten Schritt müssen Sie sicherstellen. dass gespeichert wurde return true. können Sie diese Aufgabe von einem selbst geschriebenen DocumentListener erledigen lassen: .showSaveDialog(this)) { file = openDialog. Wenn die Dokumentdaten intern in einer DocumentInstanz verwaltet werden (was bei den Swing-Textkomponenten automatisch der Fall ist). und dem Anwender gegebenenfalls noch einmal Gelegenheit zum Sichern geben. dass er vergessen hat. if (JFileChooser. // Zum eigentlichen Speichern fileSave() aufrufen if ( fileSave() == true) { // Fenstertitel aktualisieren adjustWindowTitle(). unter dem gespeichert werden soll // Dann Speichern-Befehl ausführen openDialog. dass das dirty-Feld bei Änderungen am aktuellen Dokument auf true gesetzt wird.>> GUI 393 /** * Datei speichern unter */ protected boolean fileSaveAs() { // Dateiname abfragen.

.addDocumentListener(new DocumentAdapter()). .canRead()) try { . // Neues Dokument erzeugen und in Textkomponente anzeigen PlainDocument doc = new PlainDocument().isFile() && f. dass der DocumentAdapter eine innere Klasse der Fensterklasse ist.setDocument(doc). dass der DocumentListener in den DATEI/NEU. } protected void fileOpen() { . } } } Listing 184: Ein DocumentListener zum Registrieren von nicht gespeicherten Änderungen GUI Beachten Sie..DocumentListener { private void setDirty() { dirty = true.394 >> Dateien mit Speichern-Dialog speichern /** * DocumentAdapter zur Überwachung nicht gespeicherter Änderungen */ class DocumentAdapter implements javax. if(f.getSelectedFile(). damit auf das dirty-Feld zugegriffen werden kann.. protected void fileNew() { . doc.showOpenDialog(this)) { // Datei abfragen File f = openDialog.... textpane.APPROVE_OPTION == openDialog. ..swing.event. Jetzt müssen Sie noch dafür sorgen. } public void changedUpdate(DocumentEvent e) { if (!dirty) { setDirty(). if (JFileChooser. } } public void insertUpdate(DocumentEvent e) { if (!dirty) { setDirty().und DATEI/ ÖFFNEN-Befehlen für die neuen Dokumente registriert wird. } } public void removeUpdate(DocumentEvent e) { if (!dirty) { setDirty()..

.APPROVE_OPTION == openDialog. // file und Fenstertitel aktualisieren file = null.addDocumentListener(new DocumentAdapter()).>> GUI // Dokumentlistener registrieren PlainDocument doc = (PlainDocument) textpane. ob es nicht gespeicherte Änderungen gibt.java . Da dies. } // Zuletzt verwendetes Verzeichnis auswählen openDialog. adjustWindowTitle(). doc.addDocumentListener(new DocumentAdapter()). if (JFileChooser. eingangs der fileNew().showOpenDialog(this)) { Listing 185: Aus ProgramFrame. Der dritte und letzte Schritt ist. textpane. wie Sie gleich sehen werden. // Da es in neuem Dokument keine zu speicherenden Änderungen gibt. } 395 Und wann wird dirty auf false gesetzt? Entweder wenn ein neues Dokument angelegt oder geöffnet wird (also ebenfalls in den Methoden fileNew() und fileOpen()) oder wenn gespeichert wird (also in der Methode fileSave()). nicht mit einer einfachen Abfrage von dirty getan ist. doc.. Der endgültige Code der fileNew()..setCurrentDirectory(lastDir).getDocument(). hier nothingToSave() genannt.setDocument(doc). nicht gespeicherte Änderungen // noch zu sichern if (!nothingToSave()) { return.und fileOpen()-Methoden sieht damit wie folgt aus: protected void fileNew() { // Zuerst dem Anwender Gelegenheit. nicht gespeicherte Änderungen // noch zu sichern if (nothingToSave()) { // Neues Dokument erzeugen und in Textkomponente anzeigen PlainDocument doc = new PlainDocument(). } } GUI protected void fileOpen() { // Zuerst dem Anwender Gelegenheit. // dirty auf false setzen dirty = false. empfiehlt sich die Auslagerung des Codes in eine eigene Methode.und fileOpen()-Methoden zu prüfen.

close(). return true. dann nach fileSaveAs() umleiten if (file == null) { return fileSaveAs(). out. // Da es in neu geöffnetem Dokument keine zu speichernden // Änderungen gibt. } } } protected boolean fileSave() { // Text noch nicht mit Datei verbunden. doc. if(f.close().println("Fehler beim Öffnen von " + this. // Felder und Fenstertitel aktualisieren file = f.read(in. } catch (IOException e) { System. textpane.isFile() && f. adjustWindowTitle(). } catch (IOException e) { System. f).getName()).) GUI .err. textpane.396 >> Dateien mit Speichern-Dialog speichern // Datei abfragen File f = openDialog. // Da es nach Speicherung keine zu speichernden Änderungen gibt.canRead()) try { // Text aus Datei einlesen FileReader in = new FileReader(f). dirty auf false setzen dirty = false.getSelectedFile().err. lastDir = f.getDocument().addDocumentListener(new DocumentAdapter()).file.write(out).println("Fehler beim Speichern von " + file.getName()).java (Forts. // Dokumentlistener registrieren PlainDocument doc = (PlainDocument) textpane. } try { // Text in Datei schreiben FileWriter out = new FileWriter(file). in. Listing 185: Aus ProgramFrame. // dirty auf false setzen dirty = false.

GUI . JOptionPane. wird nichts gespeichert.>> GUI 397 return false. case JOptionPane.) Drückt der Anwender die NEIN-Taste. "Änderungen speichern?". aber true zurückgeliefert (d. was bedeutet.) Bleibt noch die Methode nothingToSave().NO_OPTION: // Änderungen verwerfen return true. die Änderungen gehen verloren). nicht speichern // oder abbrechen möchte int option = JOptionPane. Die ABBRECHEN-Taste schließlich liefert false zurück und die aufrufenden Methoden werden nicht weiter ausgeführt. Verlässt der Anwender den Dialog durch Drücken der JA-Taste. die dem Anwender die Gelegenheit gibt..YES_NO_CANCEL_OPTION). nicht gespeicherte Änderungen zu sichern (aus ProgramFrame. liefert sie true zurück.java) Als Erstes prüft die Methode. öffnet die Methode einen Bestätigungsdialog. Falls nicht. (Wenn also beim Speichern alles glatt geht. die nothingToSave() aufgerufen hat (fileNew() oder fileOpen()).CANCEL_OPTION: default: // Abbrechen return false. private boolean nothingToSave() { // Keine ungespeicherten Änderungen? Dann gleich true zurückgeben if (!dirty) { return true. } } Listing 186: Methode. ob es überhaupt nicht gesicherte Änderungen gibt. wird das aktuelle Dokument mit fileSave() gespeichert und der Rückgabewert von fileSave() zurückgegeben. } } Listing 185: Aus ProgramFrame. } // Dem Anwender die Wahl lassen. ob er speichern.YES_OPTION: // Änderungen speichern return fileSave(). dass die Methode. wird true zurückgeliefert.showConfirmDialog(this. Gibt es nicht gespeicherte Änderungen. switch (option) { case JOptionPane.java (Forts. der den Anwender zum Speichern auffordert. "Texteditor".h. weiter ausgeführt wird. case JOptionPane.

awt.swing.DocumentListener { private void setDirty() { dirty = true.*.setEnabled(true). In den Methoden fileNew().event.. Der folgende Code zeigt. wenn es nicht gespeicherte Änderungen gibt.*.swing.*.. javax. // Speichern-Befehl aktivieren miSave. fileOpen() und fileSave() deaktivieren Sie den Datei/Speichern-Befehl (nach dem Setzen von dirty): // Da es in neuem Dokument keine zu speicherenden Änderungen // gibt.event. 1. 144 Unterstützung für die Zwischenablage Die Swing-Textkomponenten verfügen bereits über vordefinierte Action-Objekte zur Unterstützung der Zwischenablagebefehle. JMenuItem miCopy. javax. Speichern-Befehl deaktivieren dirty = false.398 >> Unterstützung für die Zwischenablage Abbildung 75: Aufforderung zum Speichern der letzten Änderungen Speichern-Befehl nur aktivieren.*.awt. dirty auf false setzen u. Listing 187: Programm.event. wenn es etwas zu speichern gibt Manche Anwendungen deaktivieren den Speichern-Befehl nach erfolgreicher Speicherung und aktivieren ihn erst. public class Start extends JFrame { JMenuItem miCut. das den Austausch von Text über die Zwischenablage unterstützt . GUI 2.*. } . javax. miSave.text. Ausgehend von der im vorangehenden Abschnitt beschriebenen Infrastruktur ist dieses Feature schnell eingerichtet.setEnabled(false). In der setDirty()-Methode des DocumentListeners aktivieren Sie den Speichern-Befehl: class DocumentAdapter implements javax. wie Sie die Action-Objekte mit Menübefehlen verbinden können: import import import import import java. java.swing.swing.

CTRL_MASK)). a. for(Action a : actionsArray) { if(a instanceof DefaultEditorKit. a.putValue(Action. "Ausschneiden").setMnemonic(KeyEvent. editMenu. editMenu.getKeyStroke(KeyEvent.PasteAction) { a.setEnabled(false). editMenu.getKeyStroke(KeyEvent. // JTextArea einrichten JTextArea textpane = new JTextArea(). } else if(a instanceof DefaultEditorKit. miCopy.CTRL_MASK)).ACCELERATOR_KEY. } Listing 187: Programm. a. getContentPane().getKeyStroke(KeyEvent.VK_A). miCut = new JMenuItem("Ausschneiden"). public Start() { // Hauptfenster konfigurieren setTitle("Zwischenablage für JTextArea").setEnabled(false).VK_B). BorderLayout. a.NAME.add(editMenu).add(textpane.CopyAction) { a. Event.MNEMONIC_KEY. miCut.ACCELERATOR_KEY.putValue(Action.putValue(Action.VK_K). JMenu editMenu = new JMenu("Bearbeiten"). // Menü aufbauen und als Hauptmenü des Fensters einrichten JMenuBar menuBar = new JMenuBar().putValue(Action. miPaste.setAction(a). // Befehle für die Zwischenablage hinzufügen Action[] actionsArray = textpane.putValue(Action. KeyEvent. KeyStroke. } else if(a instanceof DefaultEditorKit.putValue(Action. a.putValue(Action.setAction(a).NAME.add(miCopy).putValue(Action. KeyEvent.VK_C.setAction(a). a.CENTER).getActions(). menuBar.VK_X. KeyStroke. Event.CutAction) { a.NAME.putValue(Action. miPaste = new JMenuItem("Einfügen").add(miPaste). a. KeyEvent. "Einfügen").VK_E). "Kopieren"). a.>> GUI 399 JMenuItem miPaste. Event. miCopy = new JMenuItem("Kopieren").CTRL_MASK)). editMenu.VK_V. setJMenuBar(menuBar).MNEMONIC_KEY.add(miCut).MNEMONIC_KEY. KeyStroke.ACCELERATOR_KEY. das den Austausch von Text über die Zwischenablage unterstützt GUI .

400 >> Text drucken } setDefaultCloseOperation(JFrame. ob in der Textkomponente etwas markiert ist 145 Text drucken Seit Java 6 gibt es für das Drucken von Texten zwei Alternativen: Sie drucken schnell und bequem mit der print()-Methode der Swing-Textkomponenten. die Befehle. Sie implementieren die gesamte Druckunterstützung selbst.CopyAction.getMark()) { miCut. frame. der feststellt.addCaretListener(new CaretListener() { public void caretUpdate(CaretEvent e) { if(e.setEnabled(true).EXIT_ON_CLOSE).und Kopieren-Befehl nach Bedarf aktivieren Der Ausschneiden. frame. je nachdem. } else { miCut. ob in der Textkomponente eine Textpassage markiert wurde oder nicht. wobei die Action-Objekte für die Zwischenablagebefehle anhand ihres Datentyps (DefaultEditorKit.setEnabled(false).PasteAction) identifiziert und nach entsprechender Konfiguration mit den Menübefehlen verbunden werden. zu deaktivieren.setSize(500. miCopy. DefaultEditorKit.CutAction. Ausschneiden. } } }).300).300). zu aktivieren bzw.setEnabled(true). wenn in der zugehörigen Textkomponente auch ein auszuschneidender oder zu kopierender Text markiert ist. das den Austausch von Text über die Zwischenablage unterstützt Die getActions()-Methode der JTextArea liefert ein Array der vorinstallierten Action-Objekte zurück. DefaultEditorKit. . Listing 188: CaretListener. Es liegt daher nahe.setEnabled(false). frame.setVisible(true).setLocation(300. miCopy. } } GUI Listing 187: Programm.und der Kopieren-Befehl werden eigentlich nur benötigt. } public static void main(String args[]) { Start frame = new Start(). Dieses kann in einer Schleife durchlaufen werden. // Aktivierung / Deaktivierung der Zwischenablage-Befehle textpane. Mit Hilfe eines CaretListeners ist dies möglich.getDot() != e.

miOpen = new JMenuItem("Öffnen").addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { fileOpen()..*. javax. fileMenu.awt.attribute. JMenu fileMenu = new JMenu("Datei"). wenn Sie eine komplett eigene Druckunterstützung schreiben möchten. Im zweiten Abschnitt wird dann aufgezeigt. import .. die nicht von JTextComponent abgeleitet sind. // Menü aufbauen und als Hauptmenü des Fensters einrichten menuBar = new JMenuBar(). wenn Sie spezielle Forderungen an den Druckprozess stellen. wie Sie vorgehen können.VK_F).setMnemonic(KeyEvent. Drucken mit der print()-Methode von JTextComponent Um den Inhalt einer von JTextComponent abgeleiteten Textkomponente auszudrucken. private final String programName = "Programm". die die vordefinierte print()-Methode nicht erfüllen kann.text. java.*. // aktuell geöffnete Datei private File lastDir = null. oder wenn Sie die Inhalte von Komponenten drucken möchten.*.*. GUI public class ProgramFrame extends JFrame { .attribute. // zuletzt benutztes Verzeichnis public ProgramFrame() { . javax.java . miPrint = new JMenuItem("Drucken").. . 2. miOpen.awt. import import import import import java.MessageFormat. java. gehen Sie wie folgt vor: 1.print. private JMenuItem miPrint...print..standard. Listing 189: Aus ProgramFrame. javax.VK_D).. Importieren Sie die Pakete für die Namen der Druckklassen und -schnittstellen. Dieses Rezept behandelt zunächst das Drucken mit print(). Richten Sie den Menübefehl (gegebenenfalls auch eine Symbolleistenschaltfläche) zum Drucken ein.>> GUI 401 Den letzteren Weg werden Sie vermutlich nur beschreiten. } }).print.. private File file = null. miOpen.print.setMnemonic(KeyEvent.*.

menuBar.addSeparator(). Listing 189: Aus ProgramFrame.VK_B).add(new JobName(printname. true. attrs. .VK_P)..add(miQuit). protected void filePrint() { String printname. } }).addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { filePrint(). miQuit.java .add(MediaSizeName. null)). } }).addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System. fileMenu. miQuit. false). attrs. fileMenu.402 >> Text drucken GUI miPrint.print(new MessageFormat(printname).PORTRAIT). fileMenu.CTRL_MASK)). setJMenuBar(menuBar).add(fileMenu).) 3. fileMenu.setMnemonic(KeyEvent. Event.setMnemonic(KeyEvent. die Sie mit dem Druckbefehl verbunden haben. um den Druck zu starten. rufen Sie die von JTextComponent geerbte print()-Methode auf.add(OrientationRequested.exit(0).. null. miQuit = new JMenuItem("Beenden"). miPrint.getName(). else printname = "Unbenannt".getKeyStroke('P'.add(miPrint). // Dateiname für die Kopfzeile der Druckblätter abfragen if (file != null) printname = file. // Drucken in Schnellfassung mit Voreinstellungen für // Druckdialog und Kopf. new MessageFormat("Seite {0}"). attrs.java (Forts.ISO_A4). textpane. Listing 190: Aus ProgramFrame.setAccelerator(KeyStroke.und Fußzeile try { PrintRequestAttributeSet attrs = new HashPrintRequestAttributeSet(). In der Methode.add(miOpen). attrs. miPrint.

Wird ein Druckdialog angezeigt.add() zu übergeben) Chromaticity.COLOR new Copies(1) new Destination( new File("out. Druckdienst im Druckdialog voreingestellt ist. benutzt print() die Attribute zur Initialisierung des Druckdialogs und der Anwender kann die Druckeinstellungen verändern. } } Listing 190: Aus ProgramFrame.getMessage()).println("Drucken nicht moeglich.a.println(e.a. und eine voll konfigurierbare dritte Version: public boolean print(MessageFormat kopfzeile. Wenn kein Druckdialog angezeigt wird (drittes Argument gleich false). PrintRequestAttributeSet druckparameter. MessageFormat fusszeile.java (Forts. die Druckqualität u. System.) Von der print()-Methode gibt es drei überladene Versionen: eine parameterlose Überladung.err. Wenn Sie den Druckdialog anzeigen lassen (drittes Argument gleich true). Attribut (wie als Argument an AttributeSet.(COLOR) oder Schwarzweiß-Druck (MONOCHROME) Die Anzahl zu druckender Kopien Für die Ausgabe in eine Datei Tabelle 38: Standardattribute. die Anzahl zu druckender Kopien. damit der Java-Druckdialog angezeigt wird dem Wert null (es wird der Standarddrucker verwendet. festlegen. können Sie die Seitenorientierung. bestimmt das Argument. andere Drucker oder Druckdienste können mit Hilfe der Klasse PrintServiceLookup ermittelt werden)4 dem zuvor erstellten PrintRequestAttributeSet-Objekt mit den Druckparametern dem Wert false (Drucken ohne Statusrückmeldung) Über die Druckparameter. boolean druckdialog. . PrintService druckdienst. vom Java-Druckdialog unterstützt werden 4. bestimmt dieses Argument."). boolean interactiv) throws PrinterException GUI Letztere Version wird auch im Beispiel verwendet und mit den folgenden Argumenten aufgerufen: einem Text für die Kopfzeile (im Beispiel der Titel des zu druckenden Dokuments) einem Text für die Fußzeile (im Beispiel ein Platzhalter für die Seitenzahl) dem Wert true.toURI()) Beschreibung Farb. welcher Drucker bzw. wie sie u.und Fußzeile mitgeben können. eine zweite Version. der Sie (MessageFormat-)Texte für Kopf. über welchen Drucker oder Druckdienst ausgedruckt wird. die in Form eines PrintRequestAttributeSet-Objekts an die print()Methode übergeben werden.>> GUI 403 } catch (PrinterException e) { System.prn").err.

*. TWO_SIDED_LONG_EDGE (Buchdruck). java.DRAFT SheetCollate.awt. ONE_SIDED (Einseitig).swing.event. java. 5) Auszudruckender Seitenbereich Ausrichtung: LANDSCAPE (Querformat).print für die Namen der Druckklassen und -Interfaces. wie sie u.add() zu übergeben) new JobName("Dateiname". PORTRAIT (Hochformat).awt. kann diese das Interface implementieren.*. Wenn Sie eine der in Java vordefinierten Textkomponentenklassen verwenden.*.) Drucken mit eigener Printable-Implementierung Um selbst festzulegen. mögliche Werte sind unter anderem: MediaSizeName.awt. java.text. Richten Sie den Menübefehl (gegebenenfalls auch eine Symbolleistenschaltfläche) zum Drucken ein.awt.*. gehen Sie wie folgt vor: 1.java . REVERSE_PORTRAIT (Umgekehrtes Hochformat) Ausrichtung: DRAFT (Entwurf).*.*. import import import import import import import import java.a. lassen Sie die Fensterklasse das Interface Printable implementieren: Wenn Sie eine eigene Textkomponentenklasse definiert haben. wie der Inhalt einer Textkomponente ausgedruckt werden soll. javax.COLLATED Sides.DUPLEX Tabelle 38: Standardattribute.LANDSCAPE PrintQuality. 2. vom Java-Druckdialog unterstützt werden (Forts. die zur Aufteilung des Textes in Zeilen und Seiten benötigt werden. HIGH (Hoch) Ausrichtung: COLLATED (sortiert).NA_LETTER MediaSizeName.print.swing.ISO_B3 MediaSizeName. UNCOLLATED (nicht sortiert) Ausrichtung: DUPLEX (Duplex).ISO_A4 Beschreibung Name des Druckauftrags (üblicherweise der Name des auszudruckenden Dokuments) Priorität des Druckauftrags Größe der Druckseite. Listing 191: Aus ProgramFrame.ISO_A5 MediaSizeName.*. 3. javax.404 >> Text drucken Attribut (wie als Argument an AttributeSet. javax. java.*. beispielsweise JTextArea.ISO_A4 MediaSizeName. NORMAL (Normal).NA_8X10 new PageRanges(1. TWO_SIDED_SHORT_EDGE (Kalenderdruck) GUI OrientationRequested.util für Hilfsklassen wie Vector oder StringTokenizer.event.swing. null) new JobPriority(2) MediaSizeName. Importieren Sie das Paket java.util. REVERSE_LANDSCAPE (Umgekehrtes Querformat). Importieren Sie auch java.io.

public ProgramFrame() { . Zerlegen Sie den Text in einzelne Zeilen. private JMenuItem miPrint.. miQuit.setAccelerator(KeyStroke.VK_F). } }).addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.) 4. JMenu fileMenu = new JMenu("Datei").add(miPrint). setJMenuBar(menuBar).exit(0).CTRL_MASK)). } }). miPrint. menuBar. } }). Listing 191: Aus ProgramFrame.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { fileOpen().add(miOpen). miOpen..getKeyStroke('P'..VK_D). fileMenu.setMnemonic(KeyEvent.addSeparator(). miOpen.>> GUI 405 public class ProgramFrame extends JFrame implements Printable { ...add(fileMenu). .VK_B).setMnemonic(KeyEvent. miPrint. Event..VK_P). fileMenu. fileMenu. fileMenu.add(miQuit).. GUI .setMnemonic(KeyEvent. miQuit = new JMenuItem("Beenden"). miOpen = new JMenuItem("Öffnen"). miPrint = new JMenuItem("Drucken").setMnemonic(KeyEvent. .java (Forts. // Menü aufbauen und als Hauptmenü des Fensters einrichten menuBar = new JMenuBar().addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { filePrint(). fileMenu. miPrint.. miQuit.

equals("\n")) lines. lines. für die es sich lohnt. holt sie den Text aus der Textkomponente und speichert ihn in der lokalen Variable text. private int pages. if (line. Der erste Schritt dazu ist die Aufteilung in Zeilen. } } Nachdem die Methode eine Collection vom Typ Vector<String> zum Abspeichern der einzelnen Textzeilen angelegt hat.equals("\n")) continue.406 >> Text drucken Um einen mehrseitigen Text ausdrucken zu können. müssen Sie den Text zuerst selbst in Seiten aufteilen. private int linesPerPage. Dann beginnt die Zerlegung des Textes in Zeilen.add(line). if (line. String text = textpane.getText(). // für Druck private Vector<String> lines. true). dass die Trennzeichen nicht verworfen werden sollen. private int lineHeight.nextToken(). das den Text an den Zeileumbruchzeichen \n und \t zerlegt. eine eigene Methode zu implementieren.equals("\n") && lastToken.) . while (t.. lastToken = line.add("")..hasMoreTokens()) { String line = t.equals("\r")) continue. Hierfür bedient sich die Methode eines StringTokenizer-Objekts. GUI Die Methode zur Zerlegung des Textes könnte dann wie folgt aussehen: private void splitTextInLines() { // Text in Zeilen zerlegen lines = new Vector<String>(). if (line. StringTokenizer t = new StringTokenizer(text... (Das dritte Konstruktorargument gibt an. "\n\r". private boolean getPrintInfo. String lastToken = "". die von den verschiedenen noch zu implementierenden Druckmethoden verwendet werden können: public class ProgramFrame extends JFrame implements Printable { . Zuerst aber sollten Sie einige globale Instanzvariablen definieren. .

translate(pf. pg2. pg2. Wenn der Anwender den Drucken-Dialog aufruft.getImageableX(). stellt lines eine zeilenweise Repräsentation des Textes dar. Der Code in der while-Schleife ist etwas komplizierter als man erwarten würde. kann er dort den auszudruckenden Seitenbereich festlegen. welche Zeilen auf welcher Seite stehen. Und noch ein weiteres Problem taucht auf. public int print(Graphics pg. wie viele Seiten der Text insgesamt umfasst. pf. muss dann aber als PagePainter eine Instanz übergeben. wie hoch die Textzeilen im Zielgerätekontext sind. Implementieren Sie die print()-Methode von Printable In print() zeichnen Sie einfach die Textzeilen aus der Vector-Instanz lines nach und nach mit drawString() in den Drucker-Gerätekontext. wie es klingt. dass die Methode für jede Seitennummer. Ganz so einfach.hasMoreTokens() den Wert false zurückliefert. . In der while-Schleife wird nextToken() so oft aufgerufen. für den eigentlichen Druck. denn Sie müssen berechnen.>> GUI 407 Nachfolgende nextToken()-Aufrufe zerlegen den Text dann Stück für Stück und liefern jeweils das letzte Token zurück. Wenn die Methode zurückkehrt. Leider wird hier als Vorgabe ein Bereich von 1 bis 9999 angezeigt. für diesen die Zeilenhöhe berechnen und darauf vertrauen. Nur mit diesen Informationen können Sie print() so implementieren. int pageNr) throws PrinterException { Graphics2D pg2 = (Graphics2D) pg. Wir stehen also vor den Alternativen: die Seitenanzahl auf einer angenäherten Zeilenhöhen zu berechnen (beispielsweise könnte man sich einen Grafikkontext erstellen. gehen wir im Folgenden den zweiten Weg. Obwohl es komplizierter ist. ist dies allerdings nicht. bis der Text komplett zerlegt ist – in welchem Fall t. Dies ist aber im Grunde nur in print() möglich. und man muss vorab die Anzahl der Seiten berechnen. die korrekten Zeilen in den Gerätekontext zeichnet und den Druck beendet.setFont(f). Um aber die Anzahl der Seiten korrekt berechnen zu können. 5. dass die Höhe im Drucker-Gerätekontext nicht wesentlich von dem ermittelten Wert abweicht) print() doppelt aufzurufen: einmal um die Seitenzahl zu berechnen und ein zweites Mal GUI wie viele Zeilen auf eine Druckseite gehen. wenn ihr eine Seitennummer übergeben wird.getImageableY()). aber dies ist notwendig.size() abgefragt werden. Die Anzahl der Zeilen kann jederzeit mit lines. Man kann dies korrigieren. benötigt man die Information.getFontMetrics(). FontMetrics fm = pg2. PageFormat pf.getFont(). deren Klasse Pageable statt Printable implementiert (dies ist das geringfügigere Problem). Font f = textpane. um Zeilenumbrüche und Leerzeilen korrekt zu verarbeiten. die größer als die Anzahl der Textseiten ist. die ihr als drittes Argument übergeben wird.

wird die Zeile aus dem Vector<String>-Objekt lines berechnet. Warum ist dies notwendig? Die print()-Methode wird von der Druck-Engine aufgerufen. . ++lineIndex. linesPerPage = Math. bis die print()-Methode mit dem Rückgabewert Printable.getImageableHeight(). int lineIndex = linesPerPage * pageNr.size().NO_SUCH_PAGE. Jetzt kann ein FontMetrics-Objekt für den DruckerGerätekontext erzeugt werden. x. 6. wobei die y-Koordinate jedes Mal um den Betrag der zuvor (if-Teil) ermittelten Zeilenhöhe inkrementiert wird.size() && y < (int) pf. Dann wird der Druck beendet. Die nachfolgende while-Schleife zeichnet die Zeilen dann nacheinander in das Graphics-Objekt.getImageableWidth(). // Felder füllen lineHeight = fm. dass es die betreffende Seite nicht mehr gibt. y += lineHeight.getImageableHeight()) { String str = lines. } } // eigentliches Drucken GUI Interessant wird es in der fünften Codezeile. int x = 0. mit der die Seite beginnt. 1).getAscent(). einen Seitenformatierer und die Nummer der zu druckenden Seite übergibt.ceil((double)numberOfLines/(double)linesPerPage). while(lineIndex < lines. ob es die auszudruckende Seite überhaupt noch gibt. pages = (int) Math. Starten Sie den Druck. Der eigentliche Druck erfolgt im else-Teil. } return Printable. wo der Font des Textfelds ermittelt und in den Drucker-Gerätekontext übertragen wird. y).PAGE_EXISTS. int numberOfLines = lines. } else { if (pageNr >= pages) return Printable. int pageHeight = (int) pf.NO_SUCH_PAGE zurückmeldet.getHeight(). Ist die Seitennummer im gültigen Bereich. int y = fm.drawString(str. die ihr das Objekt für den Druckerkontext.408 >> Text drucken if (getPrintInfo) { // Druck nur vorbereiten int pageWidth = (int) pf.get(lineIndex).NO_SUCH_PAGE.max(pageHeight/lineHeight. Im anschließenden if(getPrintInfo)-Teil wird die Anzahl der zu druckenden Seiten ermittelt. das die exakten Abmessungen des Fonts im Drucker-Gerätekontext kennt. pg2. return Printable. Letztere wird einfach von null aus hochgezählt – so lange. Als Erstes wird überprüft.

printJob. die in Schritt 3 mit dem Drucken-Menübefehl verbunden wurde.Unbekannt drucken "). GUI printJob. etwas zu drucken. pages). Grundsätzlich könnte jetzt der Drucken-Dialog angezeigt und der Druck gestartet werden. book.ERROR_MESSAGE). protected void filePrint() { PrinterJob printJob = PrinterJob.setCopies(1). Dies geschieht aber noch nicht in der Absicht.append(this. Zu diesem Zweck wird im try-Block das boolesche Feld getPrintInfo auf true gesetzt und die print()-Methode des PrinterJob-Objekts aufgerufen. Nach diesem Aufruf steht die Seitenzahl fest und ist in dem Feld pages gespeichert. "Druckfehler".showMessageDialog(this. Um den Druck in Gang zu setzen.print(). "Fehler beim Drucken" + e. erzeugt ein PrinterJob-Objekt und konfiguriert es für den Ausdruck einer einzigen Kopie pro Seite.setJobName("Programm . welches – nach Zerlegung des Textes in Zeilen (splitTextInLines()-Aufruf) zusammen mit der Referenz auf das Printable-Objekt (hier das Fenster) an die setPrintable()-Methode des PrinterJob-Objekts übergeben wird. über den der Anwender in der Regel Seitengröße. doch dann würde im Drucken-Dialog noch immer die falsche Seitenzahl stehen. else printJob.getName() + " drucken"). printJob. PageFormat pf = printJob.print(). pf. try { getPrintInfo = true.setPrintable(this. was daran . JOptionPane.defaultPage()).setPageable(book).getPrinterJob(). Die Einstellungen des Anwenders werden in einem PageFormat-Objekt gespeichert.setJobName("Programm ." + file. printJob. } catch(Exception e) { JOptionPane. if (file == null) printJob. Orientierung und Ränder einstellen kann (die Drucken-Dialoge sind systemspezifisch). splitTextInLines(). sondern dient allein der Bestimmung der Seitenzahl. // Aufruf zum Festlegen der Anzahl Zeilen pro Seite Book book = new Book(). if(printJob.printDialog()) printJob. } } Die filePrint()-Methode. Anschließend wird der SEITE EINRICHTEN-Dialog aufgerufen. pf). getPrintInfo = false.>> GUI 409 Die print()-Methode wird ausschließlich von der Druck-Engine und nie direkt aufgerufen. bedienen Sie sich vielmehr eines PrinterJob.pageDialog(printJob.

Öffnen und Speichern von Textdateien (Rezepte 142 und 143). book. die allerdings nicht in jeder Java-Implementierung vorhanden sind. einen Druckbefehl (siehe Rezept 145).410 >> Editor-Grundgerüst liegt. Der append()-Methode werden dazu die für den Druck zuständige Printable-Instanz (hier das Fenster). im Folgenden einfach Buch genannt. Standard ist das Look&Feel »Metal«. Diese wurde erweitert um eine Drag-fähige JTextArea-Komponente zum Anzeigen und Bearbeiten von Dateiinhalten (Rezept 134).setPageable(book). Dann wird der Drucken-Dialog aufgerufen. Schließlich wird der Druck gestartet (print()-Aufruf). printJob. 147 Look&Feel ändern Swing unterstützt je nach Plattform mehrere Look&Feels (Designs. Unterstützung für die Zwischenablage (Rezept 144). die auf Printable beruhen. eine Statusleiste (siehe Rezepte 140 und 141). Ausgangspunkt ist eine GUI-Anwendung mit automatisch generiertem Menü und Symbolleiste (Rezept 139). pages). Den Ausweg weisen das Pageable-Interface und die Klasse Book. Das Paket com. keine Informationen über die Seitenzahl haben. ist nichts anderes als eine Sammlung von einer oder mehreren Printable-Druckaufträgen mit Seiteninformation! Die folgenden Zeilen aus filePrint() Book book = new Book(). das im Paket javax. . Da nutzt es auch nichts. Aufgrund der restriktiven Lizenzpolitik von Microsoft und Apple ist beispielsweise das Look&Feel »Windows« nur für die Windows-Versionen der Java-Laufzeitumgebung und das Look&Feel »Mac« nur für die MacVersionen verfügbar. ein PageFormat-Objekt (hier pf) und schließlich die Anzahl Seiten des Druckauftrags (hier pages) übergeben.swing untergebracht ist. die das Aussehen der Fenster und Steuerelemente festlegen). erzeugen ein Buch und fügen den zu erledigenden Printable-Druckauftrag in das Buch ein. bereits vorgestellter Rezepte zu einem rudimentären Textverarbeitungsprogramm. der Dank der Informationen aus dem Buch die korrekte Seitenzahl anzeigen kann. Methoden zum Anlegen. Anschließend wird das Buch mit setPageable() bei dem PrinterJobObjekt angemeldet. die dieses Interface implementiert. einen Info-Dialog (neu hinzugekommen).sun.java enthält weitere Look&Feels. Eine Book-Instanz. pf. GUI 146 Editor-Grundgerüst Dieses Rezept ist einfach die Kombination verschiedener. Jetzt kann der eigentliche Druck beginnen: Zuerst wird getPrintInfo auf false gesetzt. dass Druckaufträge. dass diese bereits von uns berechnet wurden.append(this.

plaf. die wie hier mit einem Nummernzeichen (#) beginnen. Look&Feel über UIManager festlegen Die Klasse UIManager erlaubt es. kann eine Ausnahme vom Typ UnsupportedLookAndFeelException auftreten. Wenn Sie ein Look&Feel mit der Methode setLookAndFeel() festlegen. das Look&Feel zur Laufzeit des Programms umzuschalten.MotifLookAndFeel com.windows.metal. da die Eigenschaftsdatei swing.setLookAndFeel() Look&Feel über swing.swing.sun.java.gtk.defaultlaf=com.sun.java.swing. Die Methode übernimmt als Parameter entweder ein Look&Feel-Objekt oder eine Zeichenfolge.properties im Verzeichnis /jdk/jre/lib an und tragen folgende Zeilen für die Look&Feels »Metal«. die den jeweiligen Klassennamen enthält und dem Wert entspricht.sun.swing. dass nur ein Look&Feel aktiv ist.plaf.java.WindowsLookAndFeel com.MetalLookAndFeel #swing.java.java. static String getSystemLookAndFeelClassName() liefert das native Look&Feel der Ausfüh- static String getCrossPlatformLookAndFeelClassName() gibt den Namen der LookAndFeelKlasse zurück. die ein plattformunabhängiges Look&Feel implementiert – das JavaLook&Feel (JLF).defaultlaf=javax.>> GUI 411 Look&Feel Metal Windows Motif GTK Klasse javax.swing.properties festlegen Bei diesem Verfahren legen Sie eine Datei swing. Achten Sie darauf.properties nur beim Starten eines Programms eingelesen wird.plaf.plaf.sun.swing. als Kommentar. defaultlaf= angegeben ist (siehe oben).motif. Von den zahlreichen Methoden dieser Klasse sind hier vor allem Folgende interessant: static void setLookAndFeel() legt ein neues Look&Feel fest.sun. der in der Eigenschaftsdatei nach swing.plaf. »Motif« und »Windows« ein: swing.swing.metal. Auf diese Weise lässt sich das Look&Feel allerdings nicht zur Laufzeit eines Programms wechseln.plaf. rungsplattform.plaf.windows.GTKLookAndFeel Tabelle 39: Vordefinierte Look&Feels Das Look&Feel können Sie mit Hilfe von zwei Verfahren ändern: statisch über die Datei swing.motif. falls das Look&Feel nicht existiert. so dass Sie die Look&Feels umschalten können.MotifLookAndFeel #swing.defaultlaf=com. Des- . indem Sie einfach das Nummernzeichen am Beginn der Zeile mit dem gewünschten Look&Feel entfernen und jeweils am Beginn der anderen Zeilen setzen.WindowsLookAndFeel Eigenschaftsdateien behandeln Zeilen.MetalLookAndFeel Standard-Look&Feel (ab Java5 in neuem »Ocean«-Design) com.swing.properties GUI dynamisch über die Methode UIManager.

die Sie nach der Festlegung des Look&Feels erzeugen. Es genügt.setLayout(new GridLayout(0. public class Start extends JFrame { ButtonListener buttonListener = new ButtonListener().updateComponentTreeUI(Container). wenn Sie den entsprechenden Abschnitt des Codes in einen try-catch-Block einschließen. import java. btnWindows. btnMotif = new JButton("Motif").createVerticalBox().add(btnWindows). getContentPane(). public Start() { // Hauptfenster einrichten setTitle("Look and Feel"). import java.h.add(Box.createGlue()). btnMetal. getContentPane().add(btnMotif). // Rechtes Panel mit Schaltflächen zum // Umschalten des Look&Feels Box rbox = Box.*.addActionListener(buttonListener).createGlue()). rbox.add(btnMetal). auch den Komponenten) mit der Methode SwingUtilities. wie das Look&Feel zur Laufzeit (der Anwender wählt einen der drei angebotenen Schalter) geändert werden kann.event.add(Box. btnWindows = new JButton("Windows"). btnMotif. btnMetal. müssen Sie das neue Look&Feel dem Hauptfenster und gegebenenfalls allen untergeordneten Fenstern (d. rbox. sind keine weiteren Vorkehrungen erforderlich.*. rbox. rbox. Ändern Sie dagegen das Look&Feel zur Laufzeit des Programms. JButton btnMotif.2)).LIGHT_GRAY).java – Demo-Programm zur Umschaltung des Look&Feels GUI .swing.addActionListener(buttonListener). Das neue Look&Feel gilt automatisch für alle Komponenten.*. btnMetal = new JButton("Metal").addActionListener(buttonListener). Das Start-Programm zu diesem Rezept demonstriert. dass Sie diese Ausnahme abfangen. // Linkes Panel zeigt lediglich zwei // Kontrollkästchen zur Demonstration des Listing 192: Start. mitteilen.setBackground(Color. rbox.awt.412 >> Look&Feel ändern halb verlangt der Compiler. import javax. btnWindows. Falls Sie das Look&Feel bereits zu Beginn einer Anwendung (beispielsweise in der main()-Methode oder im Konstruktor des Hauptfensters) einrichten.awt.

MetalLookAndFeel").java. lbox. getContentPane().swing.plaf.updateComponentTreeUI(getContentPane()).300).swing.java.setLayout(new BoxLayout(lbox.) } // Ereignisbehandlung für Schaltflächen zum Umschalten des Look&Feels class ButtonListener implements ActionListener { public void actionPerformed(ActionEvent e) { try { if (e.WindowsLookAndFeel").add(lbox).getSource() == btnWindows) UIManager. } } public static void main(String args[]) { Start frame = new Start(). getContentPane().windows.setLocation(200. setDefaultCloseOperation(JFrame.add(rbox).setSize(300. frame.add(chk2). if (e.Y_AXIS)). JCheckBox chk2 = new JCheckBox("Kursiv"). frame.getSource() == btnMotif) UIManager. JCheckBox chk1 = new JCheckBox("Unterstrichen").setLookAndFeel( "com.java – Demo-Programm zur Umschaltung des Look&Feels (Forts.plaf.sun.swing.>> GUI 413 // Erscheinungsbildes JPanel lbox = new JPanel().setVisible(true). lbox. GUI // Das neue Look&Feel allen Komponenten mitteilen SwingUtilities. frame.sun.setLookAndFeel( "javax.200).MotifLookAndFeel").EXIT_ON_CLOSE).add(chk1). BoxLayout.metal.plaf.createVerticalStrut(30)).add(Box.setLookAndFeel( "com. } catch(Exception ignore) { } . lbox.motif. lbox.getSource() == btnMetal) UIManager. if (e. } } Listing 192: Start.

event. das durch Drücken der rechten Maustaste aktiviert wird.awt. Je nach Betriebssystem gibt es verschiedene andere Bezeichnungen. ob das darunter liegende Betriebssystem überhaupt einen Systemtray unterstützt. Sicherheitshalber sollte man allerdings vor einem solchen Aufruf mit isSupported() prüfen. Es handelt sich dabei um einen speziellen Bereich auf dem Desktop (unter Windows Vista/XP typischerweise die rechte untere Ecke der Taskleiste). die sie allerdings nicht selbst anlegen kann. Sie können das anzuzeigende Symbol festlegen sowie ein Kontextmenü definieren. Die Darstellung des Programms im Systemtray erfolgt durch eine Instanz von TrayIcon.414 >> Systemtray unterstützen GUI Abbildung 76: Das Start-Programm zu diesem Rezept im Motif-. Windows. Jede Java-Anwendung kann genau eine Instanz von SystemTray besitzen. z.awt. import java. benötigen wir zwei Klassen: Systemtray.B.SystemTray zur Interaktion sowie java. das verwendete Betriebssystem bietet so etwas an. MouseMotionListener) und ActionListener (Doppelklick) registriert werden.*. Listing 193: Systemtray-Unterstützung 5. vorausgesetzt. ActionListener { private TrayIcon trayIcon.awt. indem die üblichen Listener (MouseListener.*. sondern mithilfe der statischen Methode getSystemTray() von der Java Virtual Machine anfordern muss. Das TrayIcon kann auch auf normale (linke) Mausklicks reagieren. Durch Anklicken eines solchen Symbols kann mit dem entsprechenden Programm interagiert werden. »Notification Area« etc. wo Symbole von permanent laufenden Programmen angezeigt werden. import java.und Metal-Look 148 Systemtray unterstützen Ab Version 6 unterstützt Java auch den Systemtray5. »Infobereich«.awt.TrayIcon zur Darstellung des Symbols im Um ein Programm für den Systemtray tauglich zu machen. . class SystemTrayDemo implements MouseListener. java.

) GUI . popup). "TrayIconDemo".getImage() == icons[0]) trayIcon.>> GUI 415 private Image[] icons.getImage("box1.gif").addActionListener(iconChangeListener).println("Programmende!").addActionListener(this). if(trayIcon. trayIcon.add(programEnd). } }. MenuItem programEnd = new MenuItem("Beenden").out.getDefaultToolkit().getImage("box0. // PopupMenu: ActionListener für Symbolwechsel ActionListener iconChangeListener = new ActionListener(){ public void actionPerformed(ActionEvent e) { System.setImage(icons[0]). trayIcon. icons[0] = tk.setImage(icons[1]). Toolkit tk = Toolkit.add(iconChange). System. Listing 193: Systemtray-Unterstützung (Forts.out. else trayIcon.addActionListener(endListener). MenuItem iconChange = new MenuItem("Symbol wechseln").gif"). popup. iconChange. public SystemTrayDemo() { if (SystemTray. // Symbole für Systemtray-Darstellung laden icons = new Image[2]. // PopupMenu: ActionListener für Programmende ActionListener endListener = new ActionListener() { public void actionPerformed(ActionEvent e) { System.println("Symbolwechsel"). } }. // Kontextmenü für Systemtray-Symbol einrichten PopupMenu popup = new PopupMenu().setImageAutoSize(true).addMouseListener(this).isSupported()) { SystemTray tray = SystemTray. icons[1] = tk. popup. trayIcon.exit(0). programEnd. trayIcon = new TrayIcon(icons[0].getSystemTray().

println("Doppelklick auf TrayIcon").println("TrayIcon Mausklick").err. } public void actionPerformed(ActionEvent e) { System.out. } public void mouseExited(MouseEvent e) { System.println("TrayIcon Maus exit"). } public void mousePressed(MouseEvent e) { System.println("TrayIcon Maus enter").416 >> Systemtray unterstützen // Symbol zum Systemtray hinzufügen try { tray. Bei Klick mit der rechten Maustaste erscheint ein PopupMenü mit der Option zum Wechsel des Symbols oder Beenden des Programms.) Die obige Beispielklasse registriert sich im Systemtray und lauscht anschließend auf Mausklicks auf ihrem Systemtray-Symbol6.out.out. } } else { System.out.println("Fehler: " + e).out. } catch (AWTException e) { System. } } Listing 193: Systemtray-Unterstützung (Forts. 6.add(trayIcon).println("Systemtray wird nicht unterstuetzt"). } } GUI // Behandlung der Mausereignisse auf dem TrayIcon public void mouseClicked(MouseEvent e) { System. Bei Klick mit der linken Taste wird eine Meldung im Konsolenfenster ausgegeben.println("TrayIcon Maus losgelassen").out. Das Java-Maskottchen in einer Schachtel .out.println("TrayIcon Maus gedrueckt"). } public void mouseReleased(MouseEvent e) { System. } public void mouseEntered(MouseEvent e) { System.

setFont(new Font("Arial". setResizable(false).BOLD. müssen Sie beim Programmaufruf lediglich den Parameter –splash übergeben: java –splash:dasBild.*.swing. 20)). müssen Sie der ManifestDatei einen Eintrag der Art: SplashScreen-Image: meinBild.CENTER). java. 200).png DasProgramm Als Wert für den Parameter übergeben Sie eine Bilddatei im PNG-.*.>> GUI 417 Abbildung 77: Programm-Symbol im Systemtray 149 Splash-Screen anzeigen Unter einer Splash-Screen versteht man ein Bild. GIF.*.oder JPEG-Format.jar). java. lb. setSize(300. das während des Programmstarts in der Mitte des Bildschirms angezeigt wird. Danach kann das Programm wie üblich gestartet werden (via java –jar MeinProgramm.awt.io. mit Hilfe der Klasse java.*.SplashScreen auf die angezeigte Splash-Screen zuzugreifen und beispielsweise eine Fortschrittsanzeige einzubauen import import import import java. Es gibt aber auch die Möglichkeit.awt. JLabel lb = new JLabel(" Herzlich willkommen!"). Listing 194: Anzeige eines Splash-Screens mit Fortschrittsbalken . public class Start extends JFrame { public Start() { // Hauptfenster konfigurieren setTitle("SplashScreen Demo"). Font. javax. Der Anwender hat dadurch das Gefühl. dass etwas passiert und empfindet den Startvorgang als weniger langsam. BorderLayout. Falls Sie das Programm in Form eines jar-Archivs vertreiben wollen. add(lb. Codeseitig ist also eigentlich nichts weiter zu tun.util. Die Splash-Screen wird automatisch so lange angezeigt. Um ein Bild als Splash-Screen anzuzeigen. bis das erste Fenster des Programms erscheint.png GUI hinzufügen. setLayout(new BorderLayout()).

fillRect(100.sleep(100).300). g.setVisible(true). frame. } public static void main(String args[]) { Start frame = new Start(). i <= 100.getSplashScreen().err.println(e).setColor(Color.createGraphics(). i++) { g.setPaintMode().setLocation(300. } } catch(Exception e) { System.418 >> Splash-Screen anzeigen SplashScreen splash = SplashScreen. 20).2*i. if(splash != null) { // Grafikkontext anlegen Graphics2D g = splash.200.update(). } } setDefaultCloseOperation(JFrame.setComposite(AlphaComposite. hier Programminitialisierungen durchführen Thread.BLACK). g.Clear).. splash. } } Listing 194: Anzeige eines Splash-Screens mit Fortschrittsbalken (Forts.ORANGE).fillRect(100. g. // Anzeige aktualisieren // .200. g.20). frame..200.EXIT_ON_CLOSE). // überschreib-Modus // Initialisierungen durchführen und Fortschrittsbalken updaten try { for(int i = 0.setColor(Color.) GUI . g.

>> GUI 419 Abbildung 78: Anzeige eines Splash-Screens bei Programmstart 150 Registerreiter mit Schließen-Schaltern (JTabbedPane) Die Klasse JTabbedPane (Paket javax. Leider fehlte bisher die Möglichkeit.util. import javax. import java. wie sie häufig für die Gruppierung von Programmeinstellungen oder Optionen verwendet wird.*.*.event. Dabei wird ein JTabbedPane-Objekt erzeugt. import java.*.swing. private JList fileList. Der Anwender kann die einzelnen Registerkarten über Reiter auswählen und in den Vordergrund holen.io.*. Das folgende Beispiel nutzt die neu erworbene JTabbedPane-Funktionalität zur Realisierung von Registerkarten. welches beliebig viele Registerkarten verwaltet und darstellt.swing. import java. in die Reiter der Registerkarten einzubauen. /* * JTabbedPane mit Registern. public class Start extends JFrame implements ListSelectionListener. insbesondere Schließen-Schalter.awt.*. In Java 6 wurde die Klasse JTabbedPane daher um eine Methode setTabComponentAt() erweitert. die über X-Schalter geschlossen werden können * * @author Peter Müller */ import java.event. import javax. Listing 195: JTabbedPane mit schließbaren Registern GUI .awt.*.swing) implementiert die beliebte Registerdarstellung. die der Anwender selbst hinzufügen und wieder entfernen kann. ActionListener { private JLabel curImage. Komponenten.

splitPane. curImage = new JLabel(imageIcon).100). setSize(400. "rabbit.setDividerLocation(150).setSelectionMode(ListSelectionModel.) . JScrollPane fileView = new JScrollPane(fileList).SINGLE_SELECTION). "caterpillar.EXIT_ON_CLOSE). 300).setMinimumSize(minSize). // Registerkarten einrichten tabbedPane = new JTabbedPane().setMinimumSize(minSize).add(tabbedPane. "hatter. // Bildanzeige ImageIcon imageIcon = new ImageIcon((String) fileList. fileView.gif"}. getContentPane()."duke. setResizable(false).420 >> Registerreiter mit Schließen-Schaltern (JTabbedPane) private JTabbedPane tabbedPane.gif").BorderLayout. new ImageIcon("Zoom16. splitPane. public Start() { // Hauptfenster konfigurieren setTitle("Registerkarten mit Schließen-Schaltern").gif". tabbedPane. // SplitPane erzeugen JSplitPane splitPane = new JSplitPane(JSplitPane. "rabbit2.setOneTouchExpandable(true). imageView. setLayout(new BorderLayout()). } // erzeugt ein SplitPane mit Datei-Liste und Bildanzeige JSplitPane createImagePanel() { String[] fileNames = new String[] {"alice. imageView).gif". fileList.gif". setDefaultCloseOperation(JFrame. Component>(). (Component) imagePanel). JScrollPane imageView = new JScrollPane(curImage).addTab("Bilder". // Mindestgrößen sicherstellen Dimension minSize = new Dimension(150. fileList = new JList(fileNames). fileList. GUI Listing 195: JTabbedPane mit schließbaren Registern (Forts.CENTER).gif".setSelectedIndex(0).addListSelectionListener(this).getSelectedValue()). private final JPopupMenu popupMenu = new JPopupMenu(). fileView.HORIZONTAL_SPLIT. private HashMap<JButton. JSplitPane imagePanel = createImagePanel().gif". fileList. Component> closeButtons = new HashMap<JButton.

// wenn Schalter gedrückt wird.) GUI . JButton closeButton = new JButton(xgif).add(closeButton.isPopupTrigger()) popupMenu.add(new JLabel(image)). } }). // damit Hintergrund des Reiters zu sehen ist tab.getY()). popupMenu. BorderLayout.setPreferredSize(dim).getComponent().addTab(null.getSelectedValue(). return splitPane. tab). createTab(name).addMouseListener( new MouseAdapter() { // Achtung: wenn auch auf anderen Plattformen als Windows.addActionListener(new ActionListener() { public void actionPerformed( ActionEvent e ) { String name = (String) fileList.EAST). tab. da wir unsere Komponente tab nehmen tabbedPane. Registerkarte entfernen closeButton. closeButton. JLabel title = new JLabel(name).getX(). JPanel imagePanel = new JPanel(). tab.>> GUI 421 // Popup-Menü für die Erzeugung von Registerkarten mit aktuellen Bild JMenuItem item = new JMenuItem("Bild als Register hinzufügen"). imagePanel. // dieses JPanel bildet den Registerreiter und besteht aus Bildnamen // und Schließen-Schalter JPanel tab = new JPanel(). ImageIcon xgif = new ImageIcon("xicon.add(item). dann sollte // eine analoge Methode mousePressed() implementiert werden! public void mouseReleased( MouseEvent e ) { if(e. fileList. } }). Dimension dim = new Dimension(xgif.getIconHeight() + 5). item.getSelectedValue()).addActionListener(this). e.getTabCount()-1.WEST). xgif. BorderLayout. Listing 195: JTabbedPane mit schließbaren Registern (Forts. imagePanel).setOpaque(false).getIconWidth() + 5. e.add(title. } // aktuelles Bild als neue Registerkarte hinzufügen public void createTab(String name) { ImageIcon image = new ImageIcon((String) fileList.setTabComponentAt(tabbedPane.gif").setLayout(new FlowLayout()). tabbedPane.show(e. tab. // null als Registername.

setLocation(300.setIcon(image).) Das Beispiel erzeugt eine Dateiauswahlliste.setVisible(true). dass beim Anklicken des Schalters die Registerkarte geschlossen wird. } // Ereignisbehandlungsmethode für Schließen einer dynamisch erzeugten // Registerkarte public void actionPerformed(ActionEvent e) { Component c = closeButtons.getSource()).remove((JButton) e. int index = tabbedPane. } public static void main(String args[]) { Start frame = new Start(). if(c != null) { closeButtons.removeTabAt(index).revalidate(). Klickt der Anwender mit der rechten Maustaste auf eine ausgewählte Datei.300). } } GUI public void valueChanged(ListSelectionEvent e) { if(e.getSource()). // gewähltes Bild anzeigen ImageIcon image = new ImageIcon((String) fileList. . frame.422 >> Registerreiter mit Schließen-Schaltern (JTabbedPane) closeButtons. erscheint ein Popup-Menü mit dem Befehl zum Hinzufügen einer neuen Registerkarte mit dem zugehörigen Bild. tab). tabbedPane. } } Listing 195: JTabbedPane mit schließbaren Registern (Forts. curImage.put(closeButton.indexOfTabComponent(c).get((JButton) e.getSelectedValue()).getValueIsAdjusting()) return. frame. Dem Reiter der neuen Registerkarte wird mit Hilfe der Methode setTabComponentAt() ein Schließen-Schalter hinzugefügt. Eine passende Ereignisbehandlung für den Schalter sorgt dafür. curImage.

>> GUI 423 Abbildung 79: Registerkarten mit Schließen-Button GUI .

.

sollten Sie aber bedenken.getHeight() / 2.Component. y. public static Point getCenter(Component c) { int x..Grafik und Multimedia 151 Mitte der Zeichenfläche ermitteln Um zentriert in eine Komponente zeichnen zu können. rechts – links) gleich breit sind.Insets. H i n we i s Die Klasse Point eignet sich ideal zum Abspeichern von ganzzahligen Koordinaten. import javax. return new Point(x. Multimedia Die Methode übernimmt als Argument die Referenz auf eine Komponente. Sind die Rahmenteile nicht paarweise gleich.getWidth() / 2. x = c. Die folgende Methode berücksichtigt dies: import java.JComponent. Die Rahmen von Swing-Komponenten gehören zur Komponente. muss man wissen.Point. y = c. Wenn Sie mit Swing-Komponenten arbeiten. nach der in der Methode verwendeten Formel können Sie den Mittelpunkt beliebiger Komponenten (AWT wie Swing) berechnen. deckt sich der Mittelpunkt der Komponente nicht mehr mit dem Mittelpunkt der Fläche innerhalb der Komponentenränder. Listing 197: Mittelpunkt der Fläche zwischen den Rahmen einer Swing-Komponente .awt.swing.awt. Mit obiger Methode bzw. import java. Der Zugriff auf die Felder x und y ist public. .. import java. nicht aber zum Zeichenbereich von paintComponent()! Dies stört nicht. } Listing 196: Mittelpunkt einer Komponente Grafik. dass diese womöglich einen Rahmen definieren (siehe Rezept 122). Dieser lässt sich aus Breite und Höhe leicht berechnen: import java.Point.y). an welchen Koordinaten der Mittelpunkt der Komponente liegt.awt.awt. solange die Rahmenteile paarweise (oben – unten. ermittelt deren Breite (getWidth()) und Höhe (getHeight()) und berechnet aus diesen durch Halbierung die Koordinaten des Mittelpunkts.

g.) // In Klassendefinition einer Swing-Komponente public void paintComponent(Graphics g) { super.y). int x. denn die x. g.drawLine(center.426 >> Zentrierter Text . x x y y = (c. // Fadenkreuz in Zentrum der Fläche zwischen Komponentenrändern // zeichnen center = Drawing. // Fadenkreuz in Zentrum der Komponente zeichnen Point center = Drawing.cyan). += in.top + in. an der die Grundlinie des Strings beginnt. center. den Mittelpunkt der Fläche innerhalb des Rahmens zeichnen. int y) stehen für die x.(in.top. += in. eine Scheibe in den Mittelpunkt einer Komponente bzw.getInsets()).getCenter(this). center.y).bottom))/ 2. (Beachten Sie. die keinen Rahmen haben..y-Koordinate. return new Point(x. g. Insets in) { int x.left + in. } Listing 198: Zentriert zeichnen 152 Zentrierter Text Wenn Sie einen Text zentriert in eine Komponente schreiben möchten.getCenter(this.setColor(Color.und y-Argumente von drawString (String s.getWidth() . Multimedia Der folgende Code stammt aus dem Start-Programm zu diesem Rezept und zeigt. = (c. indem Sie die x-Koordinate des Mittelpunkts berechnen (siehe Rezept 151) und an drawString() übergeben.fillOval(center. } Listing 197: Mittelpunkt der Fläche zwischen den Rahmen einer Swing-Komponente (Forts. dass für Swing-Komponenten. Dies erreichen Sie nicht.y.x.y-5. g. Wenn Sie also als x-Argument die x-Koordinate des Mittelpunkts übergeben.paintComponent(g). center. 10).. /** * Mittelpunkt der Fläche zwischen Rändern der Komponente */ public static Point getCenter(JComponent c.getHeight() .x-5. center. center.y-5.drawLine(center.) Grafik. 10.y+5). bedeutet dies in der Regel. y.x.(in. wird . this. center.x-5.left. center. wie Sie mit Hilfe der obigen Methoden ein Fadenkreuz bzw.x+5. beide Mittelpunkte zusammenfallen.right))/ 2. dass sich der Text von der Mittellinie aus gleich weit nach links und rechts ausdehnen soll.

und y-Koordinaten für zentrierte Textausrichtung // Breite minus linker und rechter Rand minus Stringbreite halbieren // wenn negativ.setFont(new Font("Georgia".drawString(text.bottom)) / 2. . Sie zeichnen den Text in die Komponente. // x. Sie richten die zu verwendende Schrift als Schrift des Grafikkontextes der Komponente ein. auf 0 setzen // oberer Rand plus halbe Schriftoberlänge hinzuaddieren y = (c. 2. so dass sich insgesamt folgende Vorgehensweise ergibt: 1. Point p = Drawing.getFontMetrics(). 30)).right) . g. Multimedia Schritt 3. sondern er beginnt an der horizontalen Mittellinie. // // // // 1 2 3 4 Grafik. Grundlinie Höhe Grundlinie Swing ist leistungsfähig Oberlänge Unterlänge Durchschuss Abbildung 80: Positionierung von Textausgaben mit drawString() Um Texte mit drawString() zentriert auszugeben. Breite und Höhe eines Textzugs hängen aber von der verwendeten Schrift ab. fm.getInsets(). das FontMetrics-Objekt und den auszugebenden Text übernimmt und wie folgt definiert ist: /** * Berechnet Ausgabekoordinaten für zentrierten Text */ public static Point centerText(JComponent c.top + insets.centerText(this. y = (y > 0) ? y : 0. p. wird hier von der Methode centerText() erledigt.>> Grafik und Multimedia 427 der String nicht horizontal zentriert.getHeight() . wird der String nicht vertikal zentriert.left+insets. Sie bestimmten mit Hilfe des FontMetrics-Objekts die Schriftabmaße und die Breite des auszugebenden Textes und errechnen dann die Koordinaten für drawString(). y.left.stringWidth(s) ) / 2.(insets.fm. g. 3.getWidth() . // Höhe minus oberer und unterer Rand halbieren // wenn negativ.y). die eigentliche Koordinatenberechnung. müssen Sie also Breite und Höhe des auszugebenden Textes berücksichtigen. String s) { Insets insets = c. auf 0 setzen // linken Rand hinzuaddieren x = (c.ITALIC. 4.(insets. die als Argumente eine Referenz auf die Komponente. Wenn Sie als y-Argument die y-Koordinate des Mittelpunkts übergeben. int x. x += insets. Font. x = (x > 0) ? x : 0. text). FontMetrics fm. FontMetrics fm = g. p.x. sondern er liegt über der vertikalen Mittellinie. Sie lassen sich vom Grafikkontext ein FontMetrics-Objekt zurückliefern.

überschreiben Sie den Inhalt. den zuvor die paintComponent()-Methode gezeichnet hat. Der Inhalt der Komponente ist nicht durch Clipping geschützt. denn der von FontMetrics.getAscent() zurückgelieferte Wert ist in der Regel um einige Pixel größer als die Höhe der Großbuchstaben. return new Point(x. So aber ergibt eine Absenkung um ein Viertel von getAscent() für die meisten Schriften eine optisch bessere Zentrierung. die Sie drawString() übergeben müssen. wenn Sie in eine Swing-Komponente zeichnen wollen. Die vertikale Zentrierung ist etwas problematisch. reichen Sie einfach nur die x-Koordinate weiter und setzen die y-Koordinate selbst.. Um in den Rahmen zu zeichnen. die keine Komponentenrahmen berücksichtigt und auch für AWTKomponenten aufgerufen werden kann: public static Point centerText(Component c. wäre eine Absenkung der Grundlinie um den halben Betrag von getAscent() korrekt. FontMetrics fm.h. H i nwe i s Grafik. sollten Sie folgende Punkte beachten: Wie im Falle von paintComponent() müssen Sie als erste Anweisung in der Methode die Basisklassenversion von paintBorder() aufrufen. String s) Abbildung 81: Zentrierte Textausgabe 153 In den Rahmen einer Komponente zeichnen Gewöhnlich überschreiben Sie die Methode paintComponent(). Multimedia In der Datei Drawing. da dieser durch das Clipping-Rechteck geschützt wird. Wenn der String lediglich horizontal zentriert sein soll. } Das zurückgelieferte Point-Objekt enthält die x.top + fm. d.und die y-Koordinate.y). wenn Sie in der Methode in den Bereich innerhalb des Rahmens zeichnen. gibt es eine eigene Methode paintBorder(). Allerdings können Sie mit dieser Methode nicht in den Rahmenbereich einer Swing-Komponente zeichnen.java zu diesem Rezept ist noch eine überladene Version von centerText() definiert.428 >> In den Rahmen einer Komponente zeichnen y += insets. Entspräche der Wert der Höhe der Großbuchstaben. um den Text horizontal und vertikal zentriert in die Komponente zu zeichnen. . Wenn Sie paintBorder() überschreiben.getAscent()/4.

H). int width = getWidth(). /** * Ovalmuster in Rahmen zeichnen */ public void paintBorder(Graphics g) { super. i+=W) gc.BLACK). j+=H) for(int i = 0. Ändern Sie den Clipping-Bereich nicht für das originale Graphics-Objekt.paintBorder(g).drawOval(i. i < insets. // Rahmen links gc. insets. width.>> Grafik und Multimedia 429 Um den Inhalt der Komponente zu schützen. j+=H) for(int i = 0. 0. i+=W) gc. j < height. j < insets. insets. for(int j = 0. H). // Rahmenbreiten abfragen Insets insets = this.bottom).setClip(0.getInsets(). W. Die folgende paintBorder()-Methode stammt aus einer abgeleiteten JPanel-Klasse.left. Entsorgen Sie die Kopie nach getaner Arbeit durch Aufruf von dispose(). sollten Sie nacheinander Clipping-Bereiche für die einzelnen Rahmenteile definieren (Java unterstützt keine kombinierten ClippingBereiche – nur Schnittmengen) und in diese zeichnen. Die Methode zeichnet ein Muster aus Ovalen in den Rahmen. W. // In die vier Seiten des Rahmens muss einzeln gezeichnet werden. insets. // da es in Java keine Möglichkeit zur Verschmelzung von Clipbereichen gibt // Rahmen oben gc.top.drawOval(i. j. i < width. // Höhe der Ovale // Kopie anlegen.top. gc. int height = getHeight(). for(int j = 0. final int W = 10.left. Multimedia . Listing 199: paintBorder() überschreiben Grafik.setColor(Color. da wir Clip-Rectangle ändern Graphics gc = g. sondern erzeugen Sie eine Kopie.create(). // Breite der Ovale final int H = 5.top). j. height-insets.setClip(0.top-insets. die einen türkisfarbenen Rahmen definiert (siehe Rezept 122 zur Erzeugung von Kompnentenrahmen).

height-insets. dies ist nicht der Fall.((height .bottom). W.right. Nun. height-insets. Vielleicht verwundert es. W. for(int j = 0. insets.right. Allerdings würde bei dieser Vorgehensweise viel Zeit damit verbracht.bottom. j.top. da der vertikale bzw. Der Schutz des Inhaltsbereichs obliegt allein den Clipping-Bereichen. H). j += H) for(int i = 0.) Der Code für das untere und das rechte Rahmenelement ist etwas komplizierter. int starty = (height .insets. j < height. width. Grafik.((width .dispose(). j+=H) for(int i = startx. dass sowohl das Clipping als auch scheinbar die Grenzen der forSchleife dafür sorgen.bottom). die for-Schleifen in Bereiche zeichnen zu lassen. i < width.top-insets. insets.right)%W). j < height.setClip(width-insets. for(int j = starty. dass nur in den Rahmen gezeichnet wird.insets.setClip(0.drawOval(i.right .insets. // Rahmen rechts gc.insets.430 >> In den Rahmen einer Komponente zeichnen // Rahmen unten gc. insets. i += W) gc. Die for-Schleifen könnten genauso gut den gesamten Bereich der Komponente durchlaufen (was im Übrigen der einfachste Weg wäre. i+=W) gc. in denen wegen des Clippings keine Ausgabe zu sehen ist. int startx = width . H). die Konsistenz des Musters zu gewährleisten). Abbildung 82: Panel mit Zeichnungen im Rahmen . i < width.bottom) . gc. horizontale Startpunkt für die Zeichenausgabe so berechnet werden muss. Multimedia } Listing 199: paintBorder() überschreiben (Forts. j.bottom)%H).drawOval(i. dass es keine Überschneidungen oder Lücken im Muster gibt.

Die Typumwandlung ist für alle Swing-Komponenten möglich. Graphics2D g2 = (Graphics2D) g. g2.0f entspricht dabei der bisherigen Normaldicke. Endpunktverzierungen und Strichmuster erzeugen können.) public void paintComponent(Graphics g) { super. Wie meist in Java gibt es aber bereits eine abgeleitete.setColor(Color. Strichstärke Um die Strichstärke festzulegen. BasicStroke verfügt über mehrere Konstruktoren. g2. . 100).>> Grafik und Multimedia 431 154 Zeichnen mit unterschiedlichen Strichstärken und -stilen Mit Java2D können Sie Strichstärke und -stil variieren. // Gelbes Rechteck mit 2-Pixel-Rahmen g2.paintComponent(g). 100). übergeben Sie dem Konstruktor einfach einen passenden float-Wert. BasicStroke In Java2D können Sie für Strichoperationen (Zeichnen einer Linie oder eines Figurumrisses) die Strichstärke vorgeben.paintComponent(g). // Java-2D verfügbar machen Graphics2D g2 = (Graphics2D) g. g2. in ein Graphics2D-Objekt umwandeln: public void paintComponent(Graphics g) { super. Die zugehörige Methode heißt setStroke() und erwartet als Argument eine Stroke-Instanz: void setStroke(Stroke s) Stroke ist eine abstrakte Klasse. 250. 250. nichtabstrakte Klasse: BasicStroke. 100. mit denen Sie Grafik. müssen Sie das Graphics-Objekt.setStroke(new BasicStroke(2. mit dem Sie arbeiten. (1. g2.BLACK).drawRect(100. Um die Vorzüge von Java2D nutzen zu können. 100.fillRect(100.0f)).setColor(Color. Multimedia »Stifte« verschiedener Breiten.YELLOW).

setColor(Color.addPoint(250. Dieses Array übergeben Sie zusammen mit der Angabe.paintComponent(g).draw(triangle). g2. Gestrichelte Linien Um gestrichelte Linien zu erzeugen.0f.addPoint(150. Multimedia // Rotes Dreieck mit abgerundetem 5-Pixel-Rahmen Polygon triangle = new Polygon(). g2. wie lang die sichtbaren und nichtsichtbaren Segmente der Linie sein sollen. als fünftes und sechstes Argument an den BasicStroke-Konstruktor.und Kreuzungspunkten bestimmen. 250). g2. ab welchem Punkt die Strichelung beginnen soll. die angeben. BasicStroke. triangle.und Kreuzungspunktstile public void paintComponent(Graphics g) { super. müssen Sie ein Array von float-Werten definieren. // Java-2D verfügbar machen Graphics2D g2 = (Graphics2D) g. triangle.setStroke(new BasicStroke(5.CAP_BUTT.setColor(Color. 50). BasicStroke.0 sein.RED).addPoint(200.) public void paintComponent(Graphics g) { super.BLACK). g2.JOIN_ROUND)). In BasicStroke sind hierfür verschiedene Konstanten definiert: Endpunktstile: CAP_BUTT CAP_ROUND CAP_SQUARE keine Endpunkte runde Endpunkte quadratische Endpunkte verbinde Segmente über ihre äußeren Kanten verbinde Segmente durch gerundete Ecken verbinde Segmente durch eine gerade Linie Kreuzungspunktstile: JOIN_MITER JOIN_ROUND JOIN_BEVEL Tabelle 40: End.fill(triangle).432 >> Zeichnen mit unterschiedlichen Strichstärken und -stilen Darstellung der Punkte Zusätzlich können Sie die Darstellung von End. (Das vierte Argument ist für eine etwaige Miter-Verbindung und muss größer oder gleich 1. 250). triangle.paintComponent(g). g2. // Java-2D verfügbar machen Graphics2D g2 = (Graphics2D) g. . Grafik.

0f. 0. dessen Klasse die Schnittstelle Paint implementiert. dashs. Grafik.setColor(Color.0f}. Vordefinierte Java-Klassen aus java.setStroke(new BasicStroke(5. 100. g2. 433 Abbildung 83: Grafische Elemente mit unterschiedlichen Strichstilen 155 Zeichnen mit Füllmuster und Farbverläufen Mit Java2D können Sie mit Füllmustern und Gradienten zeichnen.BLACK).fillOval(280.0f)). sondern auch mit Mustern und Gradienten.0f. lautet: void setPaint(Paint p) Als Argument erwartet die Methode ein Objekt. Füllungen In Java2D können Sie nicht nur mit einzelnen Farbtönen.BLUE). Um die Vorzüge von Java2D nutzen zu können.awt. malen. 100). in ein Graphics2D-Objekt umwandeln: public void paintComponent(Graphics g) { super.h. Farbübergängen. 100).drawOval(280. float[] dashs = {20.0f. Die Typumwandlung ist für alle Swing-Komponenten möglich. 5. Multimedia . Die Methode.setColor(Color. mit dem Sie arbeiten. 60. Graphics2D g2 = (Graphics2D) g. g2. sind Color – zum Zeichnen mit einem einzelnen Farbton (entspricht dem Aufruf von setColor()).>> Grafik und Multimedia // Blauer Kreis mit gestricheltem 5-Pixel-Rahmen g2. die diese Voraussetzung erfüllen.paintComponent(g).CAP_BUTT. g2. 60. müssen Sie das Graphics-Objekt. BasicStroke. d. 100. 1. BasicStroke. die das zu verwendende Füllmuster einrichtet. g2.JOIN_ROUND.

public void paintComponent(Graphics g) { super.0f. new Rectangle(0. Die Bezugspunkte müssen dabei nicht innerhalb des Objekts liegen. g2.fill(triangle). . triangle.err. } g2. 250).println("Bilddatei konnte nicht geoeffnet werden"). Multimedia // Rotes Dreieck mit Füllmuster Polygon triangle = new Polygon().CAP_BUTT.paintComponent(g). welche Abmaße die »Kacheln« haben sollen. BasicStroke.gif")).draw(triangle). in die das Muster kopiert und mit denen die später zu zeichnenden Formen ausgefüllt werden sollen. // Füllmuster aktivieren g2. // Füllmuster erzeugen TexturePaint tp = new TexturePaint(pattern. GradientPaint – zum Zeichnen mit Farbübergängen.0.addPoint(150.addPoint(200. try { // Bilddatei für Muster laden BufferedImage pattern = ImageIO. } Gradientenfüllung (GradientPaint) Bei einer Gradientenfüllung wird langsam von einer Farbe an einem Punkt zu einer anderen Farbe an einem anderen Punkt gewechselt. g2. BasicStroke.read(new File("pattern. g2. triangle. Dieser Wechsel kann sich zwischen diesen beiden Punkten vollziehen (azyklisch) oder sich wiederholen (zyklisch).JOIN_ROUND)). Grafik. 50). das gefüllt werden soll. Füllmuster (TexturePaint) Das Muster wird dem TexturePaint-Konstruktor in Form einer BufferedImage-Instanz übergeben.setPaint(tp). // Java-2D verfügbar machen Graphics2D g2 = (Graphics2D) g. } catch(IOException e) { System.addPoint(250.10)). 250). Das zweite Argument ist ein Rectangle2D-Rechteck. triangle.setColor(Color. das angibt.setStroke(new BasicStroke(5.10.434 >> Zeichnen mit Füllmuster und Farbverläufen TexturePaint – zum Zeichnen mit einem Muster.BLACK).

380.0f. 100. // Gradient aktivieren g2. 100). ob azyklisch (false) oder zyklisch (true) gefüllt werden soll. 100). Als Argumente übergeben Sie dem Konstruktor die Koordinaten der Bezugspunkte. g2.0f}. false). die beiden Farben und die Angabe. // Java-2D verfügbar machen Graphics2D g2 = (Graphics2D) g.setColor(Color. public void paintComponent(Graphics g) { super. BasicStroke.>> Grafik und Multimedia 435 Farbverläufe werden als Instanzen von GradientPaint erzeugt.BLUE).paintComponent(g).drawOval(280. 60.BLACK). Color. // Gradient definieren GradientPaint gp = new GradientPaint(280. BasicStroke. } Abbildung 84: Grafische Elemente mit unterschiedlichen Füllungen . 0.CAP_BUTT.setStroke(new BasicStroke(5. g2. 1.setPaint(gp). float[] dashs = {20.0f. Multimedia // gefüllten Kreis zeichnen g2. 60. 60.0f. // Blauer Kreis mit Gradient g2.WHITE.BLUE. Color.0f)).fillOval(280. g2.setColor(Color.JOIN_ROUND. Grafik. 100. 160. dashs. 5.

0). double dy). 20. 20). getHeight()/2).paintComponent(g).drawLine(4.drawLine(0. 5).BLACK).setColor(Color. 25. Mit exakt dem gleichen Code (wenn auch mit veränderter Farbe) wird nach der Verschiebung das Kreuz am neuen Ursprung eingeblendet. g2. 0. 25. g2.oder yRichtung zu verschieben.RED). müssen Sie das Graphics-Objekt. g2. Drehen. 20). g2.drawString("alter Ursprung". // Java-2D verfügbar machen Graphics2D g2 = (Graphics2D) g.drawString("neuer Ursprung". g2. // Ursprung in Komponenten-Mitte verschieben g2. g2. Das Java2D-Koordinatensystem In Java2D arbeiten Sie nicht mit den Pixelkoordinaten des Zielgerätekontextes. Die Typumwandlung ist für alle Swing-Komponenten möglich. 0). Graphics2D g2 = (Graphics2D) g. -5. beispielsweise durch Verschieben. Um die Vorzüge von Java2D nutzen zu können. Scherung oder Skalieren. Das folgende Code-Fragment verschiebt den Ursprung in die Mitte der Komponente. das allerdings per Voreinstellung 1:1 auf das Koordinatensystem des Zielgerätekontextes abgebildet wird. mit dem Sie arbeiten. Wenn Sie es wünschen. 5.drawLine(0. Vor der Verschiebung wird am alten Ursprung ein kleines Kreuz eingeblendet. g2. 4. 20). g2. in ein Graphics2D-Objekt umwandeln: public void paintComponent(Graphics g) { super. können Sie die Abbildung allerdings auch verändern. 5). Multimedia Translation Um alle nachfolgenden Zeichenausgaben automatisch um bestimmte Beträge in x.436 >> Zeichnen mit Transformationen 156 Zeichnen mit Transformationen Mit Java2D können Sie Grafikelemente vor dem Einzeichnen transformieren lassen. 20). 20. } . public void paintComponent(Graphics g) { super. logischen Koordinatensystem. g2. Grafik. -5. sondern mit Pixel in einem eigenen. 0.setColor(Color.drawLine(4. 4. rufen Sie die Methode translate() auf: void translate(double dx.translate(getWidth()/2.drawLine(-5.drawLine(-5.paintComponent(g). 5. 0. 0. g2.

paintComponent(g).BLACK). 15). 200.oder/und y-Richtung zu scheren. Eine Scherung in x-Richtung um 2 bedeutet.drawString("Gedreht".>> Grafik und Multimedia 437 Rotation Um alle nachfolgenden Zeichenausgaben automatisch um einen bestimmten Winkel (in Bogenmaß) zu drehen. 0.drawRect(0. 10. 10. rufen Sie die Methode rotate() auf: void rotate(double rad).translate(getWidth()/2. y). Eine Scherung um 0 führt zu keiner Veränderung. g2. 0. Das folgende Code-Fragment zeichnet zwei Rechtecke. // Java-2D verfügbar machen Graphics2D g2 = (Graphics2D) g.drawRect(0. g2. Das folgende Code-Fragment zeichnet zwei Rechtecke: eins links. Negative Winkel drehen entgegen dem Uhrzeigersinn. y) -> (x + 2 * y.setColor(Color. 200. g2. double shy). // Java-2D verfügbar machen Graphics2D g2 = (Graphics2D) g. Multimedia g2.rotate(Math.toRadians(-45)). public void paintComponent(Graphics g) { super.drawString("Original". 100). Die Drehung erfolgt immer um den Ursprung des Koordinatensystems. } Scherung Um alle nachfolgenden Zeichenausgaben automatisch in x. rufen Sie die Methode shear() auf: void shear(double shx. Das zweite Rechteck wird mit Scherung 1 in x-Richtung gezeichnet. g2.setColor(Color. dass zu jeder x-Koordinate das Zweifache des vertikalen Abstands zum Ursprung hinzuaddiert wird: (x. // Ursprung in Panel-Mitte verschieben g2. // Drehung des KOS um 45 Grad entgegen Uhrzeigersinn g2. Die Scherung erfolgt immer relativ zum Ursprung des Koordinatensystems. Grafik. 15). public void paintComponent(Graphics g) { super. eins rechts vom Ursprung. Das zweite Rechteck ist gegenüber dem ersten um 45 Grad entgegen dem Uhrzeigersinn gedreht. 100). positive Werte im Uhrzeigersinn.RED).paintComponent(g). getHeight()/2). . g2.

g2. g2. 35).translate(getWidth()/2. 110.drawOval(-50. getHeight()/2). public void paintComponent(Graphics g) { super. 100.RED). Das folgende Code-Fragment zeichnet zwei Kreise um einen gemeinsamen Mittelpunkt. -40). 35. g2. g2. } . 100.setColor(Color.oder yRichtung zu skalieren. 100. 100). Der zweite Kreis wird gegenüber dem ersten um den Faktor 2 in x.wie in y-Richtung vergrößert gezeichnet. g2.RED). -140.shear(1. 35. 35).drawString("Vergrößert".translate(getWidth()/2. 100. -50. 0). g2.und Y-Richtung) g2.drawOval(-50. g2.scale(2. getHeight()/2).paintComponent(g).drawString("Original". -50.drawString("Geschert". 2).BLACK).drawString("Original".setColor(Color.drawRect(-150. g2. double sy). // Scherung der X-Achse g2. 150). g2. // Java-2D verfügbar machen Graphics2D g2 = (Graphics2D) g.drawRect(100. } Grafik. -100. g2.setColor(Color. 150). -40). g2. -100. // Ursprung in Panel-Mitte verschieben g2.setColor(Color.438 >> Zeichnen mit Transformationen // Ursprung in Panel-Mitte verschieben g2. rufen Sie die Methode scale() auf: void scale(double sx.BLACK). Multimedia Skalierung Um alle nachfolgenden Zeichenausgaben automatisch um bestimmte Faktoren in x. 100). // Skalierung (Vergrößerung um 2 in X. g2.

1 Rad gedreht und um 60 Pixel in x-Richtung verschoben! E x k ur s .setTransform(oldAT). wenn diese Schriftart verfügbar ist.5)..>> Grafik und Multimedia 439 Abbildung 85: Gedrehtes Rechteck und gedrehter Text Wenn Sie die ursprüngliche Koordinatentransformation wieder herstellen möchten. 0.scale(0. // Ursprüngliche Transformation wiederherstellen g2. ob eine bestimmte Schriftart verfügbar ist Grafik. /** Listing 200: Methode. 157 Verfügbare Schriftarten ermitteln Welche Schriftarten auf dem aktuellen System installiert sind und verwendet werden können.5. speichern Sie diese vorab und rekonstruieren Sie sie mit setTransform(): // Ursprüngliche Transformation sichern AffineTransform oldAT = g2.GraphicsEnvironment. erscheinen nachfolgende Zeichenausgaben um 0. // Eigene Transformationen hinzufügen g2. Multimedia Die Tranformationsmatrix Alle Koordinatentransformationen werden addierend in eine interne Transformationsmatrix eingetragen. »Times New Roman« oder »Verdana« entgegen und liefert true zurück. Die nachfolgend definierte Utility-Methode isFontAvailable() nimmt als Argument einen Schriftfamilien-Namen wie »Arial«. die prüft.getTransform(). ermitteln Sie mit Hilfe der getAvailableFontFamilyNames()-Methode der Klasse GraphicsEnvironment. .. Wenn Sie also nacheinander eine Translation um 10 Pixel in xRichtung.1 Rad und eine weitere Translation um 50 Pixel in xRichtung vornehmen. import java.awt. eine Rotation um 0.

awt. Multimedia Im Beispielverzeichnis zu diesem Rezept finden Sie neben dem Start-Programm. JCheckBox(). import java. JButton().java. noch ein weiteres Programm PrintFonts. String fontNames[] = ge.equals(fontName)) return true. List(). ob eine bestimmte Schriftart verfügbar ist (Forts. welches über die Kommandozeile einen Schriftartennamen entgegennimmt und mit Hilfe der obigen Methode feststellt. welche Fonst auf System verfügbar sind GraphicsEnvironment ge = GraphicsEnvironment. import java. // prüfen.java .*.*. private private private private private private List fontList List sizeList JCheckBox btnBold JCheckBox btnItalic JButton btnOK JButton btnCancel = = = = = = new new new new new new List().440 >> Dialog zur Schriftartenauswahl * Prüfen. ob der übergebene Font verfügbar ist */ public static boolean isFontAvailable(String fontName) { // Abfragen.event. /* * Fontdialog.awt. Sie können mit ihrer Hilfe auch ein Listenfeld oder einen Dialog zur Schriftartenauswahl durch den Anwender einrichten.swing. die prüft.) Grafik.getAvailableFontFamilyNames(). 158 Dialog zur Schriftartenauswahl Mit Hilfe der GraphicsEnvironment-Methode getAvailableFontFamilyNames() lässt sich nicht nur prüfen.*. import javax. ob Font in Liste vorhanden for(String s : fontNames) if (s. return false. mit dem Sie die Liste der auf dem System installierten Fonts komplett ausgeben können. ob die betreffende Schriftart verfügbar ist. JButton(). Listing 201: Dialog zur Schriftartenauswahl . auf dem aktuellen System verfügbar ist.Dialog zur Auswahl einer Schriftart */ public class Fontdialog extends JDialog implements ActionListener { private Font chosenFont.getLocalGraphicsEnvironment(). JCheckBox(). die Sie für eine Textausgabe oder den Text einer GUI-Komponente nutzen wollen. } Listing 200: Methode. ob eine Schriftart.

"72" }.length. 12)). getRootPane(). 2.getLocalGraphicsEnvironment().setBounds(new Rectangle(270. "28". 159)). i < fonts.awt. "26".setFont(new java.setBounds(new Rectangle(63. Listing 201: Dialog zur Schriftartenauswahl (Forts. "11".getAvailableFontFamilyNames(). 120.setFont(new java. 100. fontList.setText("OK"). 25)).select(0). for(int i = 0.Font("Dialog". ++i) fontList.awt. btnCancel.select(0). btnOK. 115. btnItalic.setText("Abbrechen"). "12".setText("Kursiv"). welche Fonts auf System verfügbar sind GraphicsEnvironment ge = GraphicsEnvironment.addActionListener(this). String fonts[] = ge. 204. 10. i < sizes.setLayout(null). 25)). btnBold. "48". // Font-Liste mit auf System installierten Fonts füllen fontList. 10.add(fonts[i]). setModal(true). 100. 265)). setTitle("Schriftart auswählen").setDefaultButton(btnOK).addActionListener(this).setBounds(new Rectangle(14. 1. // Schalter zum Verlassen des Dialogs btnOK. 150.setBounds(new Rectangle(270. sizeList. btnCancel. "24". ++i) sizeList. "20". "36". "10". btnItalic. 100. btnBold. btnOK. "16". 115.add(sizes[i]). 100)). 12)).setBounds(new Rectangle(210. setResizable(false).setText("Fett").>> Grafik und Multimedia 441 /** * Konstruktor */ public Fontdialog(JFrame f) { super(f). Multimedia . getContentPane().length. 204. // Größen-Liste mit vordefinierten Größen füllen String sizes[] = {"8". for(int i = 0.) Grafik.setBounds(new Rectangle(270. btnItalic. "18".Font("Dialog". 25)). "22". "9". sizeList. setSize(new Dimension(386. btnCancel. 244. // Schalter für fett und kursiv btnBold. 25)). // Abfragen. "14".

getContentPane().getSelectedItem()).add(fontList.equals("Abbrechen")) { chosenFont = null. Zwei JCheckBox-Schalter erlauben die Aktivierung von Fett. -größe und -stil zusammengetragen und es wird ein entsprechendes Font-Objekt erzeugt.PLAIN.) Grafik. Das Font-Objekt kann jederzeit (solange der Dialog besteht) mit der Dialog-Methode getFont() abgefragt werden.add(btnCancel. style. getContentPane().BOLD. null).getActionCommand(). null).getSelectedItem(). setVisible(false). Das Start-Programm zu diesem Rezept zeigt ein einfaches JTextArea-Textfeld. chosenFont = new Font(name.add(btnItalic.add(btnOK. if(label.und Kursivschrift. getContentPane(). null).equals("OK")) { String name = fontList. null). Über den Menübefehl DIALOG/SCHRIFTARTEN kann eine Instanz von FontDialog eingeblendet und eine Schrift für das Textfeld ausgewählt werden. (Hinweis: Die Instanzierung des Dialogs erfolgt im Konstruktor des Fensters.442 >> Dialog zur Schriftartenauswahl getContentPane().isSelected()) style |= Font. setVisible(false). if (btnItalic. werden die Einstellungen für Schriftart.isSelected()) style |= Font.add(sizeList. } } Listing 201: Dialog zur Schriftartenauswahl (Forts. Drückt der Anwender den OK-Schalter. } } /** * Auf Anfrage den ausgewählten Font zurückliefern */ public Font getFont() { return chosenFont. if (btnBold.parseInt(sizeList.add(btnBold. int size = Integer. null). getContentPane(). } else if(label. null).ITALIC. int style = Font. } /** * Ereignisbehandlung für Schalter */ public void actionPerformed(ActionEvent e) { String label = e. Multimedia Der Dialog besteht aus zwei List-Instanzen. über die der Anwender Schriftart und Schriftgröße auswählen kann. size). getContentPane().) .

addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { // Schriftarten-Dialog anzeigen fontDialog. als Schatten. Listing 202: Aus Start.y + 100)).setVisible(true).setFont(f). // Schriftart abfragen Font f = fontDialog.>> Grafik und Multimedia 443 miFonts. dass der Schatten zuerst und dann der eigentliche Text gezeichnet werden muss: .getFont(). } }). Multimedia Abbildung 86: Dialog zur Schriftauswahl 159 Text mit Schattenwurf zeichnen Eine einfache Technik.setLocation((getLocation(). Zu beachten ist lediglich. ein wenig horizontal und vertikal versetzt und in meist blasserer Farbe. fontDialog.x + 100).java – Ereignisbehandlung zu Dialog/Schriftarten-Befehl Grafik. (getLocation(). ist das zweimalige Zeichnen des Texts: einmal als eigentlicher Text und dann noch einmal. Text mit Schattenwurf zu zeichnen. // Schriftart für JTextArea übernehmen if (f != null) textpane.

Color. 60. int x. die horizontale und vertikale Verschiebung des Schattens. x+dx. g. Color. 5.drawShadowText(text.drawString(s. Ein typischer Aufruf sähe damit etwa wie folgt aus: public void paintComponent(Graphics g) { super.444 >> Text mit Schattenwurf zeichnen /** * Text mit Schatten zeichnen */ public static void drawShadowText(String s. die Koordinate der Textausgabe. 6. Color shadowColor) { // Zuerst den Schatten zeichnen g. Font. g. } Abbildung 87: Text mit Schattenwurf .setColor(textColor). Multimedia Als Argumente übergeben Sie der drawShadowText()-Methode den auszugebenden Text.paintComponent(g).BLACK. g. y+dy). // Dann den Text zeichnen g.setColor(shadowColor). int dy. int y.BOLD. // Text mit Schatten zeichnen Drawing. das zum Zeichnen zu verwendende Graphics-Objekt.drawString(s. y). } Listing 203: Methode. g. x. Graphics g.LIGHT_GRAY). die Text mit Schatten zeichnet Grafik. Color textColor. die Farben für Text und Schatten. int dx.setFont(new Font("Georgia". 40)). 30.

java – eine Panel-Klasse.util.*.>> Grafik und Multimedia 445 160 Freihandzeichnungen Es gibt verschiedene Techniken.awt.awt. import import import import java. // Wenn Maus gedrückt. In der paintComponent()-Methode werden die Punkte aus der ArrayList<Point>-Collection in das Panel gezeichnet.*. javax. Die Panel-Klasse stellt eine Methode clear() zum Löschen der Zeichnung bereit.mouseDragged-Ereignis).event. in die der Benutzer mit der Maus zeichnen kann . Multimedia A c h t u ng Um einen guten Schatteneffekt zu erzielen.black).ArrayList. wie man das Zeichnen von Freihandlinien unterstützen kann.*. sollte die Schrift nicht zu schmal und der Versatz des Schattens im Vergleich zur Schriftbreite nicht zu groß sein. wenn die Anwendung mit dem Panel nach einer Minimierung oder Verdeckung wieder in den Vordergrund geholt wird. Auf diese Weise wird die Freihandzeichnung rekonstruiert. Wenn der Anwender die Maustaste drückt (MouseListener. Gleichzeitig werden die Koordinaten des Punkts in einer ArrayList<Point>-Collection gespeichert. java. an Mausposition Punkt zeichnen addMouseListener(new MouseAdapter() { Listing 204: FreehandPanel. Grafik. java. /** * Panel für Freihandzeichnungen */ public class FreehandPanel extends JPanel { // Collection zum Speichern der gezeichneten Punkte private ArrayList<Point> points = new ArrayList<Point>(37). /** * Der Konstruktor implementiert das Maushandling und setzt * die Hintergrundfarbe */ FreehandPanel() { setBackground(Color. Der in diesem Rezept verfolgte Ansatz sieht so aus: Die gesamte Unterstützung für das Zeichnen von Freihandlinien ist in einer von JPanel abgeleiteten Klasse zusammengefasst. wird an der Position der Maus ein Punkt gezeichnet.swing.mousePressed-Ereignis) oder die Maus mit gedrückter Maustaste bewegt (MouseMotionListener.

Listing 204: FreehandPanel.WHITE).fillRect(p.getY()). } }).setColor(Color. g.dispose(). // An Mausposition Punkt zeichnen Graphics g = ((JPanel) e. } Grafik. 2). // An Mausposition Punkt zeichnen Graphics g = ((JPanel) e. g.getX(). } }).y. points.446 >> Freihandzeichnungen private Point p.java – eine Panel-Klasse.dispose(). an Mausposition // Punkt zeichnen addMouseMotionListener(new MouseMotionAdapter() { private Point p. points.getComponent()). p.add(p).y.setColor(Color. 2).getGraphics().x. g.add(p). e. g.) . p.clear(). in die der Benutzer mit der Maus zeichnen kann (Forts. 2. e. // Wenn Maus mit gedrückter Maustaste bewegt wird.getComponent()).WHITE).getX(). g. Multimedia /** * Löscht die Zeichnung */ public void clear() { // Die gespeicherten Punkte löschen points. public void mousePressed(MouseEvent e) { // Punkt speichern Point p = new Point(e. 2.getY()).fillRect(p.x.getGraphics(). public void mouseDragged(MouseEvent e) { // Punkt speichern Point p = new Point(e. g.

y.x.WHITE).fillRect(p.java – eine Panel-Klasse. p. 2). g.setColor(Color. } } } Listing 204: FreehandPanel.>> Grafik und Multimedia 447 // Neuzeichnen repaint().paintComponent(g).) Abbildung 88: Frei gezeichneter Charakterkopf Grafik. 2. } /** * In paintComponent() die Freihandzeichnung bei Bedarf rekonstruieren */ public void paintComponent(Graphics g) { super. for(Point p : points) { g. in die der Benutzer mit der Maus zeichnen kann (Forts. Multimedia .

deren Daten aber noch nicht geladen wurden.448 >> Bilder laden und anzeigen 161 Bilder laden und anzeigen Das Laden und Anzeigen von Bildern ist grundsätzlich nicht schwierig. Dies bringt Laufzeitvorteile.imageio.getDefaultToolkit().awt. try { Image pic = ImageIO. Dass wir diesem Thema ein eigenes Rezept widmen.*. wenn ein. liegt nicht nur daran. } Das vom ImageIO. // Bild laden mit Toolkit und MediaTracker Image pic = Toolkit. einer von Image abgeleiteten Klasse. nach Anstoß über einen MediaTracker (siehe auch Rezept 244): import java. In beiden Fällen liefert die Methode direkt nach Aufruf ein Image-Objekt zurück.read() zurückgelieferte Bildobjekt ist vom Typ BufferedImage. } catch (InterruptedException ignore) { } Grafik.oder URL-Objekts geladen werden können: import javax. die für Anwendungen von der Klasse Toolkit und für Applets von der Klasse Applet angeboten wird. // Ladevorgang im Hintergrund starten. ist aber unnötig.getImage("demo. Seit dem SDK 1.addImage(pic. . ohne zu warten. 1). Multimedia Eine Besonderheit der getImage()-Version der Klasse Toolkit ist. tracker.jpg"). // Bilder zur Überwachung des Ladevorganges an einen MediaTracker übergeben MediaTracker tracker = new MediaTracker(this). sondern auch an den vielfältigen Facetten. Bilder laden Es gibt In Java viele Wege.jpg")). das zwar mit einer Bilddatei verbunden ist. } catch(IOException e) { e. mit denen sich das Thema variieren lässt.. dass die geladenen Bilder in einem Cache zwischengespeichert und bei Bedarf wieder verwendet werden.read(new File("background.waitForID(1). InputStream..und dasselbe Bild mehrfach angefordert wird. Dies geschieht erst bei der ersten Verwendung bzw. . wenn ein Bild nur einmal geladen und angezeigt wird.. mit deren Hilfe Bilder einfach und bequem durch Übergabe eines File-. Bilder zu laden. try { tracker. H in we i s .printStackTrace(). Der traditionelle Weg führt über die Methode getImage().ImageIO. dass es sich um eine wichtige Grundtechnik handelt..4 gibt es zudem die Klasse ImageIO.

getHeight(this). this.java – Panel-Klasse zum Anzeigen von Bildern . import javax. übergeben Sie Breite und Höhe des Bilds: g. image. } } Grafik. public ImagePanel(Image image) { this. } // Bild in Panel zeichnen public void paintComponent(Graphics g) { g. public ImagePanel(Image image. 0. image.drawImage(image. import java. und 5.swing.swing.getWidth(this).JPanel { private Image image.awt. Im ersteren Fall übergeben Sie der drawImage()-Methode als Breite und Höhe des Zielbereichs (4.drawImage(image.JPanel { private Image image. 0. boolean scale) { this. Parameter) die Breite und Höhe des Panels. welches angibt.image = image. JPanel-Instanzen ein Bild direkt als Hintergrundbild zuzuweisen (siehe auch Rezept 117). ob das Bild an die Panel-Größe angepasst (true) oder vollständig angezeigt (false) werden soll.getHeight(). this.).image = image.JPanel. Soll dagegen das Panel an die Bildgröße angepasst werden. Zur Unterstützung von Layout-Manager wird die Methode getPreferredSize() überschrieben. private boolean scale. } /** * Bild in Panel zeichnen Listing 205: ImagePanel. this. müssen Sie eine eigene Panel-Klasse ableiten und in der paintComponent()-Methode das Bild in das Panel zeichnen: public class ImagePanel extends javax.>> Grafik und Multimedia 449 Bilder anzeigen Bilder werden üblicherweise in JPanel-Instanzen angezeigt (außer es handelt sich um Symbole für Schalter etc. Ihr Konstruktor übernimmt als zweites Argument neben dem anzuzeigenden Bild ein Flag. Da es keine Möglichkeit gibt. this). Die nachfolgend definierte Klasse ImagePanel kann beides. this). 0.*. /** * Panel zur Darstellung von Bildern */ public class ImagePanel extends javax.getWidth().scale = scale. 0. Multimedia Die Gretchenfrage dabei lautet: Soll das Bild an die Größe des Panels oder umgekehrt das Panel an die Größe des Bilds angepasst werden.swing.

image. Das Programm zu diesem Rezept demonstriert verschiedene Möglichkeiten der Einbettung von Bildern in GUI-Oberflächen.getHeight(this)). this. this. true).getHeight()).drawImage(image.java – Panel-Klasse zum Anzeigen von Bildern (Forts.drawImage(image.10. else return new Dimension(image. Dann betten Sie es in den Container ein: // ImagePanel einrichten und in ContentPane einbetten ImagePanel imagePanel = new ImagePanel(pic.) Um ein Bild in einer ImagePanel-Instanz anzuzeigen. this. 0. imagePanel. übergeben das anzuzeigende Image und die gewünschte Skalierung. this).getHeight(this). 0. image. getContentPane(). Anschließend müssen Sie das ImagePanel-Objekt gegebenenfalls noch positionieren (übergeordneter Container arbeitet ohne Layout-Manager) und dimensionieren (Bild wird an Panel-Größe angepasst).getWidth(this). ob das ImagePanel von einem FlowLayout-Manager (erstes Argument = »j«) oder direkt positioniert und dimensioniert werden soll (»n«) und ob das ImagePanel an die Größe des Bilds (zweites Argument = »n«) angepasst werden oder die eigene Größe behalten soll (»j«). Beim Aufruf teilen Sie dem Programm mit.450 >> Bilder laden und anzeigen */ public void paintComponent(Graphics g) { if(scale) g.getWidth(this).add(imagePanel). 100). 0. rufen Sie einfach den Konstruktor auf.setBounds(10. else g.getHeight(). . } } Listing 205: ImagePanel.getWidth(). die bevorzugte Größe * zurückliefern */ public Dimension getPreferredSize() { if(scale) return new Dimension(this.getWidth(). 0. 100. this). image. Multimedia /** * Falls ein Layout-Manager eingesetzt wird. } Grafik.

seine Größe wird an die Bildgröße angepasst (hier sogar größer als das Fenster). das Bild wird auf die ImagePanel-Größe skaliert.>> Grafik und Multimedia 451 Abbildung 89: java Program j j – das ImagePanel wird von einem FlowLayout-Manager zentriert. . Grafik. Multimedia Abbildung 90: java Program j n – das ImagePanel wird von einem FlowLayout-Manager zentriert.

weil es vom Anwender über einen Datei-Dialog ausgewählt wurde). das Bild wird auf die ImagePanel-Größe skaliert. weswegen nur die linke obere Ecke zu sehen ist. Abbildung 92: java Program n n – das ImagePanel wird ohne Layout-Manager direkt positioniert und dimensioniert (setBounds()-Aufruf). müssen Sie vor dem Laden die alte ImagePanel-Instanz entfernen. . nach dem Laden den Layout-Manager aktivieren und das Fenster neu zeichnen.452 >> Bilder laden und anzeigen Grafik. das Bild wird nicht skaliert. Multimedia Abbildung 91: java Program n j – das ImagePanel wird ohne Layout-Manager direkt positioniert und dimensioniert (setBounds()-Aufruf). Bilder mit Datei-Dialog öffnen und anzeigen Wenn das anzuzeigende Bild wechseln kann (beispielsweise.

getContentPane(). import javax.add(imagePanel). Multimedia Diashows Wenn Sie mehrere Bilder laden wollen.swing.showOpenDialog(this)) { // Datei abfragen File f = openDialog.file. die Bilder lädt und verwaltet H inwe is Mehr Informationen zum Öffnen von Dateien mit dem Datei-Dialog finden Sie in Rezept 142. scale).APPROVE_OPTION == openDialog. if(f.setBounds(10. import java. .doLayout(). die mehrfach.java (siehe Unterverzeichnis mit Datei-Dialog) Grafik. repaint().getSelectedFile().*. } } } Listing 206: Aus Program_withOpen.awt.getName()).*. // letztes Bild vorab entfernen if(imagePanel != null) getContentPane().*. an verschiedenen Stellen oder abwechselnd (Diashow) angezeigt werden sollen. imagePanel. 100). import java. // Fenster aktualisieren getContentPane(). können Sie sich entweder der Toolkit-Methode getImage() bedienen oder die Bilder mit ImageIO.println("Fehler beim Öffnen von " + this.err.canRead()) { // hier wird das Bild geladen try { pic = ImageIO. } catch (IOException e) { System.isFile() && f.event. 100.>> Grafik und Multimedia 453 if (JFileChooser.read() laden und selbst in einem Cache zwischenspeichern. Genau dies macht die nachfolgend abgedruckte Klasse ImageManager (die im Übrigen auch für Applets verwendet werden kann.read(f).awt. siehe Rezept 244).10.remove(imagePanel). Listing 207: ImageManager-Klasse. // ImagePanel einrichten und in ContentPane einbetten imagePanel = new ImagePanel(pic.

} } } /* * Index des aktuellen Bildes zurückliefern */ public int currentImage() { return current. javax. String. imageFilenames) { Image pic = null.) . if (pic != null) images.. die Bilder lädt und verwaltet (Forts.io.printStackTrace()..ImageIO.Vector. private MediaTracker tracker.applet.. /* * Konstruktor für Applets */ ImageManager(JApplet applet. java.Applet. private boolean isApplet = false. } /* * Index auf nächstes Bild vorrücken und zurückliefern */ public int nextImage() { Listing 207: ImageManager-Klasse. public class ImageManager { private Vector<Image> images = new Vector<Image>(5).. siehe Rezept 244 } Grafik. String. private int current = 0. Multimedia /* * Konstruktor für GUI-Anwendungen */ ImageManager(JFrame frame.util.imageio.. java.read(new File(filename)).. // images-Collection füllen for (String filename : imageFilenames) { // Image-Objekt erzeugen und in Vector-Collection speichern try { pic = ImageIO. imageFilenames) { // .454 >> Bilder laden und anzeigen import import import import java.add(pic). } catch (IOException e) { e.*.

. die Bilder lädt und verwaltet (Forts. } if (index >= 0 && index <= images. siehe Rezept 244 return images.size()-1. Das Pendant zum Konstruktor. } return null. Das Programm Diashow. } } Listing 207: ImageManager-Klasse. ist die Methode getImage().>> Grafik und Multimedia 455 current++. der über den zweiten Parameter ein Array oder eine Auflistung von Bilddateinamen übernimmt. der die Bilder lädt. Grafik. return current. return current. if (current >= images.size()) { // Wenn Image für Applet ist // . nextImage() und previousImage() zum Durchlaufen der Bildersammlung verwendet werden kann.size()) current = 0. der in Kombination mit den Methoden currentImage(). if (current < 0) current = images.java nutzt die Klasse ImageManager zur Implementierung einer einfachen Bildergalerie.) Der Konstruktor. Ansonsten unterhält die Klasse noch einen internen Positionszeiger current. } /* * Index auf vorheriges Bild zurücksetzen und zurückliefern */ public int previousImage() { current--.get(index). Multimedia /* * Bild mit dem angegebenen Index zurückliefern */ public Image getImage(int index) { . erzeugt für jede Bilddatei ein Image-Objekt und speichert diese in einer internen Vector-Collection.. die auf Anfrage ein geladenes Image aus der internen Vector-Collection zurückliefert.

"pic03. // Anzeigebereich top = new JPanel(new FlowLayout(FlowLayout.jpg"). import javax.EXIT_ON_CLOSE). JButton btnPrevious = new JButton("Voriges Bild").*.swing. getContentPane(). JPanel bottom. public class Diashow extends JFrame { ImageManager images.previousImage())). top.doLayout().awt.CENTER)). images = new ImageManager(this.*.CENTER)).jpg".*.awt.event. /* * Bildersammlung füllen und ContentPane einrichten */ public Diashow() { setTitle("Bildbetrachter"). // Navigationsschalter bottom = new JPanel(new FlowLayout(FlowLayout. .456 >> Bilder laden und anzeigen import java. } }). // Bildersammlung DisplayPanel display.setImage(images. top.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { // Vorangehendes Bild anzeigen lassen display. "pic02. import java. JButton btnNext = new JButton("Nächstes Bild"). Listing 208: TheApplet. Multimedia setDefaultCloseOperation(JFrame. // Panel zum Anzeigen der Bilder JPanel top.jpg". display = new DisplayPanel(). } /* * Zweiteilige ContentPane für eine Bildergalerie * oben: DisplayPanel * unten: Navigationsschalter */ class ContentPane extends JPanel { public ContentPane() { setLayout(new BorderLayout()).add(display).java implementiert mit Hilfe von ImageManager eine Bildergalerie. "pic01. Grafik.add(new ContentPane()).getImage(images. btnPrevious.

CENTER). pic. Multimedia /* * Panel zum Anzeigen der Bilder * */ private class DisplayPanel extends JPanel { Image pic = null. 0. // Neuzeichnen repaint(). } public void paintComponent(Graphics g) { super. Listing 208: TheApplet. BorderLayout.add(btnNext).getHeight(this)). 0.currentImage())). // Größe des Panels an Bild anpassen this.setImage(images. } }).getImage(images. (Forts.paintComponent(g).getHeight(this).getWidth(this). pic.add(btnPrevious).setImage(images.getWidth(this).SOUTH).>> Grafik und Multimedia 457 btnNext.drawImage(pic.java implementiert mit Hilfe von ImageManager eine Bildergalerie. // Anfangs das erste (aktuelle) Bild aus der Bildersammlung anzeigen display. add(top.nextImage())).getWidth(this). top. /* * Neues Bild anzeigen */ public void setImage(Image pic) { this. bottom. add(bottom.setPreferredSize(new Dimension(pic. this.doLayout(). // Bild in Panel zeichnen if (pic != null) g. } } Grafik.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { // Nächstes Bild anzeigen lassen display. this).getHeight(this))).) . BorderLayout. bottom. pic. pic.setSize(pic.getImage(images.pic = pic.

300).und setRGB()-Methoden von BufferedImage Im ersten Fall lassen Sie sich von der getGraphics()-Methode ein Graphics-Objekt zurückliefern und zeichnen dann mit den üblichen Grafikmethoden in das Image. Multimedia Abbildung 93: Diashow 162 Bilder pixelweise bearbeiten (und speichern) Es gibt zwei Wege. zeichnen können: Über das Graphics-Objekt des Image-Objekts Über die setData().100).WHITE). Image-Objekte.java implementiert mit Hilfe von ImageManager eine Bildergalerie. .getGraphics(). g.500).458 >> Bilder pixelweise bearbeiten (und speichern) } } public static void main(String args[]) { Diashow frame = new Diashow(). // selbst erzeugte Graphics-Objekte müssen entsorgt werden g.100.dispose(). Graphics g = image.setLocation(300. d. (Forts. frame. frame.) Grafik.fillRect(100.100. frame.setSize(600. g.setColor(Color.setVisible(true).h. wie Sie in Bilder. } } Listing 208: TheApplet.

BLUE). (Color. können Sie vorab prüfen.getReaderFormatNames()) System. if (iter. profitieren Sie zudem davon. try { ImageIO. } H in we is Von der ImageIO-Methode getWriterFormatNames() können Sie sich eine Liste der auf einem System registrierten ImageWriter ausgeben lassen: for(String s : ImageIO. new File("dateiname. } catch (IOException e) { e.getRGB()). der Sie die Koordinaten des Pixels und den RGB-Wert der gewünschten Farbe übergeben: image. new File("dateiname. BufferedImage image. »bmp«.setRGB(i. ob das gewählte Speicherformat auf dem aktuellen Rechner unterstützt wird. } Als zweiten Parameter übergeben Sie einen String mit der Bezeichnung für das gewünschte Speicherformat (beispielsweise »JPEG«.printStackTrace().. ob Sie nicht besser gleich alle Image-Vorkommen in BufferedImage umwandeln. Um ein Image-Objekt in ein BufferedImage-Objekt umzuwandeln. Etwas eleganter geht es. »BMP«).getImageWriters(new ImageTypeSpecifier(image).printStackTrace(). "jpg". } catch (IOException e) { e. »png«. "JPEG". »jpeg«. ist es dennoch möglich: Sie zeichnen einfach ein ausgefülltes Rechteck der Breite und Höhe 1. sprich ob ein ImageWriter für das Format verfügbar ist: try { Iterator iter = ImageIO.write()-Methode als Datei auf die Festplatte speichern können..hasNext()) ImageIO. (Überlegen Sie sich aber. dass Sie Ihre Bilder mit Hilfe der ImageIO. »BMP«. »JPG«.jpg")). Wenn der Benutzer H in we is Grafik. "jpeg"). . Bilder speichern Wenn Sie mit BufferedImage-Objekten arbeiten.write(image. besorgen Sie sich von getGraphics() eine Referenz auf das Graphics-Objekt des BufferedImage und kopieren Sie dann mittels drawImage() den Inhalt des Image-Objekts in das BufferedImage-Objekt.write(image. »jpg«. Multimedia .jpg")). wenn es sich bei dem Image um ein Objekt der abgeleiteten Klasse BufferedImage handelt.>> Grafik und Multimedia 459 Einzelne Pixel einfärben Obwohl Graphics keine eigene Methode zum Einfärben einzelner Pixel vorsieht. Dann brauchen Sie nur deren setRGB()-Methode aufrufen. »gif«.out.) Das Start-Programm zu diesem Rezept demonstriert anhand einer Fraktalberechnung (berechnet wird eine Julia-Menge) die pixelweise Einfärbung eines BufferedImage.println(s). Wenn Sie möchten. j.

i++) { for(int j = 0.getWidth(). wird ein neues BufferedImage-Objekt erzeugt.. das dieselben Abmaße hat wie das Panel.BLACK. // Fraktalberechnung starten fractal = new Thread(Start.getWidth().getHeight(). // Fraktalberechnung starten und abbrechen btnStart.TYPE_INT_RGB).setColor(c). // Fraktal berechnen und einzeichnen for(int i = 0. int height = display. private DisplayPanel display. int width = display.RED. fractal. in dem das Fraktal angezeigt wird. /** * Julia-Menge berechnen und in display-Panel sowie image einzeichnen */ public void run() { Graphics g = display. j < height. . if(Math. BufferedImage... In der run()-Methode des Threads werden die Farbwerte zur Einfärbung der Pixel berechnet. i < width. private JButton btnSave.getGraphics(). Dann wird der Thread für die Fraktalberechnung in Gang gesetzt: class Start extends JFrame implements Runnable { private Thread fractal.getHeight()..addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if(fractal == null) { Grafik. public Start() { .this).abs(x) < 1) c = Color.. // Pixel in Panel einfärben g.. Multimedia // Image-Objekt erzeugen image = new BufferedImage(display. else c = Color. private BufferedImage image = null.. private JButton btnStart..460 >> Bilder pixelweise bearbeiten (und speichern) die Berechnung startet.start(). Mit der errechneten Farbe werden dann die Pixel im Panel und im BufferedImage gesetzt. . display. j++) { . .

addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { try { Iterator iter = ImageIO. "jpeg"). 461 Drückt der Anwender nach Abschluss der Berechnung den Speichern-Schalter. } catch (IOException ioe) { ioe. c.printStackTrace().1).>> Grafik und Multimedia g. } . wird das Fraktal als JPEG-Datei gespeichert: // Fraktal als "fractal.getImageWriters( new ImageTypeSpecifier(image). Listing 209: Aus Start..fillRect(i. Multimedia .jpg" speichern btnSave. "JPEG". j.hasNext()) ImageIO.write(image.setRGB(i..1.jpg")).getRGB()).java Abbildung 94: Julia-Menge Grafik. } } }). // Pixel in Image einfärben image. new File("fractal. if (iter.j.

462 >> Bilder drehen 163 Bilder drehen Leider gibt es in Java keine direkte Unterstützung für das Drehen von Bildern. Verschieben Sie das Koordinatensystem des Graphics2D-Objekts um die Breite des Originals nach unten und drehen Sie es dann 90 Grad um den Ursprung. Damit die gedrehte Kopie korrekt und vollständig angezeigt wird. 4. indem man zum Schluss die Kopie in das Original zurückschreibt). das Koordinatensystem des Grafikkontextes zu drehen und zu verschieben – und damit ist es möglich. dann die Kopie erzeugt und schließlich die Kopie in eine neue ImagePanel-Instanz und diese in die ContentPane des Fensters eingebettet. wobei die Kopie anfangs durch ein leeres. nichtgedrehtes Bild repräsentiert wird. gedrehte Kopien zu erstellen (oder auch das Original zu drehen. Besorgen Sie sich ein Graphics2D-Objekt für den Grafikkontext des BufferedImage-Objekts. Multimedia Original 90 Grad-Drehung Verschiebung Abbildung 95: Bilddrehung um 90 Grad Das Start-Programm zu diesem Rezept zeigt Original und gedrehte Kopie nebeneinander in eigenen Panels an. Grafik. 3. das so breit ist wie das zu drehende Bild hoch (und umgekehrt so hoch wie das Original breit). dass der einkopierte Bildinhalt wieder im ursprünglichen Anzeigebereich liegt (siehe Abbildung 95). Erzeugen Sie ein neues BufferedImage-Objekt. 2. . Um beispielsweise ein Bild um 90 Grad zu drehen. siehe Rezept 161) zuerst entfernt. Es gibt aber in Java2D die Möglichkeit. Kopieren Sie den Inhalt des Originalbilds in das BufferedImage-Objekt. gehen Sie wie folgt vor: 1. Die Kombination aus Verschiebung und Drehung des Koordinatenursprungs führt dazu. wird das alte Panel (eine ImagePanel-Instanz.

createGraphics(). // Anzeige aktualisieren getContentPane(). i1.getWidth().toRadians(-90)). g.add(i2Panel).doLayout(). "Platzhalter-Bild entfernen getContentPane(). i1. // Panel mit Bild neu einfügen i2Panel = new ImagePanel(i2.>> Grafik und Multimedia 463 // Altes bzw. g.getWidth()). // Graphics-Objekt beschaffen Graphics2D g = i2.rotate(Math.drawImage(i1. Listing 210: BufferedImage i2 als gedrehte Kopie von BufferedImage i1 erstellen Abbildung 96: Original und gedrehte Kopie Grafik.getHeight(). so dass // "gedrehtes" Bild wieder im Anzeigebereich g. false). // Inhalt von i1 hineinkopieren g. null). repaint(). 0. // Koordinatensystem drehen und verschieben. 0. getContentPane(). // Neues Bild erzeugen (mit vertauschter Breite und Höhe) i2 = new BufferedImage(i1.remove(i2Panel). Multimedia . i1.dispose().translate(0.getType()).

getWidth(). kopieren Sie entsprechend die Farbinformation des Pixels (i.translate(i1. Listing 211: Klasse zum Spiegeln von Bildern . können Sie auch so vorgehen. at. int height = org. müssen Sie lediglich die Farbinformationen links und rechts der Achse tauschen. height. g. public class ImageUtil { public static final int X_AXIS = 0. j) aus dem Originalbild in das Pixel (width-1-i. Dazu durchlaufen Sie in einer doppelten Schleife alle Pixel und kopieren die Farbinformation des Pixels (i.getHeight().transform(at).getHeight()). dass Sie ein Objekt der Klasse AffineTransform erzeugen.*. import java. Wenn Sie das Bild an seiner vertikalen Mittelachse spiegeln wollen. In diesem Falle können Sie für Drehungen um Vielfache von 90 Grad die optimierte Methode quadrantRotate() verwenden.transform(at). Grafik. at. g. Multimedia 164 Bilder spiegeln Um ein BufferedImage-Objekt an seiner horizontalen Mittelachse zu spiegeln. i1.getWidth()). der Sie einfach das Vielfache n übergeben: // Drehung um -90 Grad AffineTransform at = new AffineTransform().getWidth(). i1.quadrantRotate(3).image. // Drehung um -180 Grad AffineTransform at = new AffineTransform().awt. // Instanzbildung unterbinden private ImageUtil() { } H i nwe i s /** * Erzeuge gespiegelte Kopie von Image org */ public static BufferedImage mirror(BufferedImage org. g. int axis) { int width = org.464 >> Bilder spiegeln Wenn Sie viel mit kombinierten Koordinatentransformationen arbeiten. BufferedImage dest = new BufferedImage(width.quadrantRotate(2). j) der gespiegelten Kopie. in diesem die gewünschten Transformationen speichern und dann das Objekt an die Graphics2D-Methode transform() übergeben.translate(0. org. at. j) aus dem Originalbild in das Pixel (i. height-1-j) der gespiegelten Kopie.getType()). public static final int Y_AXIS = 1.

remove(i2Panel). j. Multimedia . j++) dest. } } Listing 211: Klasse zum Spiegeln von Bildern (Forts.) Das Start-Programm zu diesem Rezept zeigt Original und gespiegelte Kopie nebeneinander in eigenen Panels an.add(i2Panel). i++) for(int j = 0. height-1-j. i < width. org.X_AXIS). } return dest. i++) for(int j = 0. // Anzeige aktualisieren getContentPane(). j < height.mirror(i1. wobei die Kopie anfangs durch ein leeres.getRGB(i.setRGB(width-1-i.getRGB(i. getContentPane(). // Altes bzw. i < width. wird das alte Panel (eine ImagePanelInstanz. "Platzhalter-Bild entfernen getContentPane(). j < height.setRGB(i. org.doLayout(). j)). siehe Rezept 161) zuerst entfernt. repaint(). Damit die gespiegelte Kopie korrekt angezeigt wird. nichtgedrehtes Bild repräsentiert wird. // i2 in ImagePanel und ContentPane einbetten i2Panel = new ImagePanel(i2. ImageUtil. dann die Kopie erzeugt und schließlich die Kopie in eine neue ImagePanel-Instanz und diese in die ContentPane des Fensters eingebettet. j++) dest. j)). Listing 212: BufferedImage i2 als gespiegelte Kopie von BufferedImage i1 erstellen Grafik.>> Grafik und Multimedia 465 if(axis == X_AXIS) { for(int i = 0. // Gespiegelte Kopie von i1 erzeugen und in i2 speichern i2 = ImageUtil. } else if (axis == Y_AXIS) { for(int i = 0. false).

import java. getInstance(ColorSpace. das Sie für den gewünschten Farbraum erstellen. import java.color.*. Etwas bequemer geht es mit der nachfolgend definierten Methode changeColorSpace().CS_sRGB) für RGB-Farben. Die Methode erzeugt dann selbstständig eine Kopie des Originals für den neuen Farbraum und liefert die Referenz auf die Kopie zurück. null). ColorConvertOp co = new ColorConvertOp(ColorSpace.image.awt. der Sie einfach eine Referenz auf das Originalbild und den gewünschten Farbraum übergeben. Den Farbraum erzeugen Sie als Instanz der Klasse ColorSpace – beispielsweise ColorSpace.getInstance(ColorSpace.*.CS_sRGB).ColorSpace.awt. import java.awt. BufferedImage dest) Als Argumente übergeben Sie das Originalbild (src) und eine Referenz auf die Kopie. BufferdImage filter(BufferedImage src. public class ImageUtil { Listing 213: Methode zur Änderung des Farbraums .CS_GRAY) für Graustufen oder ColorSpace. Multimedia Abbildung 97: Original und gespiegelte Kopie 165 Bilder in Graustufen darstellen Der Farbraum eines BufferedImage-Objekts kann mit Hilfe der filter()-Methode von ColorConvertOp verändert werden.466 >> Bilder in Graustufen darstellen Grafik. deren Farbraum geändert werden soll (dest).getInstance(ColorSpace. Aufgerufen wird die filter()-Methode über ein ColorConvertOp-Objekt.

return co.io. g. 0.getHeight(). 0.applet. Das Ergebnis ist aber in der Regel nicht so gut.*.. ColorSpace cs) { // Kopie erzeugen BufferedImage dest = new BufferedImage(org. Beachten Sie bitte hierbei. org. import java.createGraphics(). null). d.newAudioClip() an.getType()).*.) Grafik.getWidth(). weil die Methode die Kopie anfangs ohne Farbrauminformation erstellt. g. Multimedia 166 Audiodateien abspielen Audiodateien mit AudioClip abspielen Für das Abspielen von kleinen Audiodateien im AIFF-.filter(org. mit deren Hilfe ein AudioClip-Objekt erzeugt wird. Mit der Methode play() kann man sie dann abspielen1: import javax. null). dass play() nichtblockierend ist. WAV. H in we is Die filter()-Methode von ColorConvertOp kann auch selbstständig eine Kopie des Originals anlegen.*. während im Hintergrund die Audiodaten gespielt werden. org.h.oder AU-Format bietet sich die statische Methode Applet. dest).>> Grafik und Multimedia 467 // Instanzbildung unterbinden private ImageUtil() { } /** * Erzeuge Kopie von Image org mit geändertem ColorSpace */ public static BufferedImage changeColorSpace(BufferedImage org.swing.dispose(). import java. // Inhalt von org nach dest kopieren Graphics2D g = dest. . // ColorSpace ändern ColorConvertOp co = new ColorConvertOp(cs. welches eine Audiodatei komplett in den Hauptspeicher lädt. Listing 214: Abspielen von kleinen Audiodateien 1.drawImage(org. im Programmfluss geht es sofort weiter. } } Listing 213: Methode zur Änderung des Farbraums (Forts.

} Grafik.setVisible(true).out. die Daten werden sofort abgespielt.sampled.h. wenn die Audiodatei sehr groß ist. eine Datei) bis zum .h. } Start frame = new Start(). Dies bringt eine teilweise deutliche zeitliche Verzögerung mit sich. bevor etwas zu hören ist.setSize(500.300).300). Das zentrale Element des Pakets ist javax. d.DataLine. AudioClip clip = Applet.sampled an. dass sie wie bereits erwähnt die abzuspielenden Daten komplett lädt.exit(0).B. Multimedia try { // Audiodatei laden File f = new File(args[0]).play().468 >> Audiodateien abspielen /** * Abspielen von kleinen Audiodateien als Clip */ public class Start extends JFrame { public Start() { setTitle("Audio"). setDefaultCloseOperation(JFrame. In solchen Fällen bietet sich daher der Einsatz des Pakets javax. frame. bevor das Abspielen beginnt. } catch(Exception e) { e.println(" Aufruf: Start <Audiodatei>"). mit dem man Audiodaten im so genannten Streaming-Modus verarbeiten kann.sound.printStackTrace(). während noch weiter geladen wird. d. Ferner kann es zu Speicherproblemen kommen. System.EXIT_ON_CLOSE). welches einen Teil der Audio-Pipeline darstellt – meistens einfach Line genannt.toURI(). frame.sound.setLocation(300..toURL()).length != 1) { System.newAudioClip(f. // Audiodatei abspielen clip. } public static void main(String args[]) { if (args. den Musik von der Quelle (z.) Streaming-Audio Die AudioClip-Klasse hat den Nachteil. frame. } } Listing 214: Abspielen von kleinen Audiodateien (Forts. ein Teil des Wegs.

/** * Klasse zum Abspielen von beliebig großen Klangdateien (wav.>> Grafik und Multimedia 469 Ziel (z. format. } /** * Audio ausgeben auf Lautsprecher */ public void play() { try { File file = new File(fileName).PCM_SIGNED.B.io. Multimedia .getChannels().getFormat(). } /** * Lautstärke einstellen * * @param n Anzahl dB der Verstärkung */ public void setVolume(int n) { volume = n.a.sampled.Encoding. import java. /** * Konstruktor * * @param file Dateiname */ public Sound(String file) { fileName = file. // Konvertierung falls nicht-lineares PCM if(format.SourceDataLine. die Lautstärke regulieren lässt. Lautsprecher) durchlaufen muss. mit denen sich u.*. Diese Verbindung erfolgt mit Hilfe von javax.Encoding. Eine Line kann dabei zusätzliche Kontrollobjekte (vom Typ javax. Zum Abspielen von Audiodaten muss man mittels eines AudioInputStream-Objekts die Daten lesen und an den Lautsprecher schicken. AudioInputStream inStream = AudioSystem.Control) besitzen.getSampleSizeInBits(). import javax.PCM_SIGNED) { AudioFormat tmp = new AudioFormat( AudioFormat.sound. au) */ class Sound { private String fileName.sound.sound.getEncoding() != AudioFormat.getSampleRate(). format.sampled. AudioFormat format = inStream. private int volume = 1. Listing 215: Sound.getAudioInputStream(file). 2 * format.*.java Grafik.sampled.

getControl(FloatControl.Info info = new DataLine. fc = (FloatControl) line. line. Listing 216: Abspielen von Audiodaten im Streaming-Verfahren . Multimedia while(num != -1) { num = inStream. num).setValue(volume).470 >> Audiodateien abspielen 2 * format.) Das Start-Programm zeigt.start().drain().java (Forts.getFrameRate().getFrameSize(). int num = 0. wie man mit Hilfe der Klasse Sound eine Audiodatei abspielen kann. } } } Listing 215: Sound. public class Start { public static void main(String[] args) { if(args.length != 1) { System. format). 0. DataLine. inStream).class.open(format). format = tmp.close(). line.MASTER_GAIN). inStream = AudioSystem.write(audioPuffer. } catch(Exception e) { e.out. System.printStackTrace().getAudioInputStream(format.Type.println("Aufruf: <Audiodatei>"). if(num >= 0) line. format. } line. true).getLine(info). FloatControl fc. byte[] audioPuffer = new byte[10000]. audioPuffer. 0. line = (SourceDataLine) AudioSystem.exit(0).length). // warten bis Ausgabe beendet line. } SourceDataLine line = null. fc. } Sound s = new Sound(args[0]).Info(SourceDataLine.read(audioPuffer. Grafik.

>> Grafik und Multimedia

471

s.setVolume(3); // Lautstärke s.play(); } } Listing 216: Abspielen von Audiodaten im Streaming-Verfahren (Forts.)

167 Videodateien abspielen
Für das Abspielen eines Videos muss man auf das Java Media Framework (JMF) zurückgreifen, welches über den Link http://java.sun.com/products/java-media/jmf/2.1.1/download.html heruntergeladen werden kann. Das JMF wird dabei in zwei Varianten angeboten: eine plattformunabhängige (cross-platform) sowie eine Windows- bzw. Solaris-spezifische Version, die erweiterte Möglichkeiten2 und bessere Performance bietet. Die plattformunabhängige Version ist ein ZIP-Archiv (aktueller Name jmf-2_1_1e-alljava.zip) und enthält die Datei jmf.jar, die Sie in den CLASSPATH aufnehmen müssen. Die plattformspezifischen Varianten sind allerdings in der Regel die wesentlich bessere Wahl und werden mit ihrem eigenen Setup-Programm (z.B. für Windows: jmf-2_1_1e-windowsi586.exe) eingerichtet. Sie installieren automatisch alle notwendigen jar-Dateien im JDK-Heimatverzeichnis und passen den Standard-CLASSPATH entsprechend an. Das Java Media Framework ist eine recht umfangreiche und komplexe API und wir beschränken uns hier auf das absolute Minimum an Klassen aus dem Paket javax.media, um ein Video auf den Bildschirm zu zaubern. Die zentrale Klasse heißt MediaPlayer und kann über eine statische Methode Manager.createPlayer() erzeugt werden, wobei man die Datenquelle als URL mitgeben muss. Dadurch kann die Datenquelle eine Internetadresse sein oder auch eine lokale Datei. Das MediaPlayer-Objekt hat schon die komplette Funktionalität in sich gekapselt. Man muss lediglich noch einen besonderen Listener – ControllerListener – bei ihm registrieren und in dessen Listener die Methode controllerUpdate() implementieren. Diese Methode wird immer dann aufgerufen, wenn das MediaPlayer-Objekt seinen internen Zustand geändert hat. Das wichtigste Ereignis, das es zu behandeln gilt, nennt sich RealizeCompleteEvent und besagt, dass alle Vorbereitungen abgeschlossen sind und das Abspielen beginnen kann. Zur Anzeige bietet MediaPlayer eine Methode getVisualComponent() an, in der das Video angezeigt wird. Diese Komponente (vom Typ java.awt.Component) kann man dann in seine Benutzeroberfläche einbauen. Ferner gibt es noch getControlPanelComponent(), mit der eine besondere Steuerungskomponente (Anhalten, Positionierung) zur Verfügung gestellt wird.

Tipp

Zum Abspielen von Audiodateien (inklusive MP3) kann auch das Java Media Framework eingesetzt werden, wie im nächsten Rezept gezeigt wird.

2.

Mit den Performance-Packs kann man z.B. auch Videos aufnehmen. Außerdem werden mehr Video- und Audioformate unterstützt. Ein Vergleich der Features findet sich hier: http://java.sun.com/products/java-media/jmf/2.1.1/ formats.html

Grafik, Multimedia

472 >> Videodateien abspielen

import import import import import import import

javax.swing.*; javax.media.*; java.io.*; java.awt.*; java.awt.event.*; java.net.*; java.util.*;

/** * Frame mit Audio-/Video-Unterstützung */ class ProgramFrame extends JFrame implements ControllerListener, ActionListener { private Player player; private Component control; private JPanel viewPanel; private JPanel labelPanel; public ProgramFrame() { setTitle("AudioVideo-Demo"); setLayout(new BorderLayout()); // Menüleiste erstellen JMenuBar mb = new JMenuBar(); JMenu fileMenu = new JMenu("Datei"); JMenuItem fileMenuPlay = new JMenuItem("Öffnen"); fileMenuPlay.addActionListener(this); fileMenu.add(fileMenuPlay); mb.add(fileMenu); setJMenuBar(mb); JLabel label = new JLabel(""); labelPanel = new JPanel(); labelPanel.add(label); add(labelPanel,BorderLayout.NORTH); setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); } // Mausbehandlung zur Dateiauswahl public void actionPerformed( ActionEvent e ) { // Dateinamen auswählen JFileChooser chooser = new JFileChooser(); String[] extensions = {"mpg","wav","mp3"}; MyFileFilter filter = new MyFileFilter(extensions); chooser.setFileFilter(filter); int choice = chooser.showOpenDialog(ProgramFrame.this); if (choice == JFileChooser.APPROVE_OPTION) { File file = chooser.getSelectedFile(); if(file != null) Listing 217: Abspielen von Video mit Java Media Framework

Grafik, Multimedia

>> Grafik und Multimedia

473

play(file); } } // Abspielen einer Audio/Video-Datei public void play(File file) { if(player != null) player.stop(); try { // Anzeige für Dateinamen aktualisieren labelPanel.remove(0); JLabel label = new JLabel(file.getName()); labelPanel.add(label);

} catch(Exception e) { e.printStackTrace(); } } // Anzeige von Audio/Video public void controllerUpdate(ControllerEvent e) { if(e instanceof RealizeCompleteEvent) { // Player ist mit Vorbereitungen fertig // evtl. alte Ansicht und Steuerung entfernen if(viewPanel != null) remove(viewPanel); if(control != null) remove(control); // AWT Komponente mit Bild Component view = player.getVisualComponent(); // Anzeige des Bildes falls es ein Video ist if(view != null) { viewPanel = new JPanel(); viewPanel.add(view); add(viewPanel, BorderLayout.CENTER); } control = player.getControlPanelComponent();

Listing 217: Abspielen von Video mit Java Media Framework (Forts.)

Grafik, Multimedia

player = Manager.createPlayer(file.toURL()); player.addControllerListener(this); player.start();

474 >> Videodateien abspielen

if(control != null) ; add(control, BorderLayout.SOUTH); pack(); } } } /** * FileFilter für Dateiauswahl-Box */ class MyFileFilter extends javax.swing.filechooser.FileFilter { private HashMap<String,String> extensions; private String description; public MyFileFilter(String[] ext) { description = ""; extensions = new HashMap<String,String>(); Grafik, Multimedia for(int i = 0; i < ext.length; i++) { if(ext[i].startsWith(".")) ext[i] = ext[i].substring(1); if(ext[i].startsWith("*.")) ext[i] = ext[i].substring(2); extensions.put(ext[i], ext[i]); description += " *." + ext[i] + ","; } } public String getDescription() { return description.substring(0,description.length()); } public boolean accept(File f) { if(f != null) { if(f.isDirectory()) return true; else { String name = f.getName(); int pos = name.indexOf("."); if(pos < 0) return false; String ext = name.substring(pos+1); if(extensions.get(ext) != null) return true; Listing 217: Abspielen von Video mit Java Media Framework (Forts.)

>> Grafik und Multimedia

475

else return false; } } else return false; } } Listing 217: Abspielen von Video mit Java Media Framework (Forts.)

Das vorgestellte Programm eignet sich auch zum Abspielen von Musikdateien, und zwar nicht nur die üblichen WAV-Dateien, sondern auch das beliebte MP3! Es wird dann lediglich die Control-Leiste gezeigt und natürlich kein Bild, da der Aufruf von getVisualComponent() den Wert null zurückgibt.

168 Torten-, Balken- und X-Y-Diagramme erstellen
Grafik, Multimedia

Die grafische Darstellung von Daten in Form von Diagrammen kann zu recht aufwändiger Programmierarbeit führen, bis halbwegs zufrieden stellende Resultate erzielt werden. Aus diesem Grund sollte man zuerst schauen, ob es eine OpenSource-Bibliothek gibt, welche die gewünschten Anforderungen abdeckt. Der bekannteste Vertreter ist JFreeChart. Diese Bibliothek bietet eine Vielzahl von Diagrammen an, z.B. Torten- und Balkengrafik, X-Y-Plots sowie viele weitere, teilweise recht spezielle Darstellungsformen. Um JFreeChart einzusetzen, müssen Sie von http://www.jfree.org/jfreechart das ZIP-Archiv jfreechart-1.0.4.zip3 herunterladen und daraus die jar-Dateien jcommon-1.0.8.jar und jfreechart-1.0.04.jar extrahieren und in den CLASSPATH Ihrer Java-Anwendung aufnehmen. Das Grundgerüst für den Einsatz der Bibliothek bilden folgende Klassen:
JFreeChart: repräsentiert ein Diagramm. Dataset, DefaultPieDataset, CategoryDataset, XYSeriesCollection u.a. definieren die darzu-

stellenden Zahlenwerte.

ChartFactory bietet statische Methoden zur Erzeugung des gewünschten Diagrammtyps. ChartPanel ist eine von JPanel abgeleitete Klasse zur grafischen Anzeige eines Diagramms.

Die Methoden der nachfolgend definierten Klasse Chart zeigen, wie diese Klassen kombiniert werden müssen, um Torten-, Balken- oder X-Y-Diagramme zu erstellen. Das jeweils zurückgelieferte ChartPanel-Objekt kann dann direkt wie eine gewöhnliche Swing-Komponente an der gewünschten Stelle in die Benutzeroberfläche eingefügt werden.
import import import import import org.jfree.chart.*; org.jfree.data.*; org.jfree.data.xy.*; org.jfree.data.category.*; org.jfree.data.general.*;

Listing 218: Chart.java – Hilfsklasse zum Erstellen verschiedener Diagrammtypen
3. Aktueller Dateiname im Juni 2005

476 >> Torten-, Balken- und X-Y-Diagramme erstellen

import org.jfree.chart.plot.*; import org.jfree.util.*; import javax.swing.*; import java.awt.*; import java.awt.event.*; /** * Klasse zum Generieren von Torten-, Balkendiagrammen und X-Y Plots */ class Chart { /** * Die Daten als 2D-Tortengrafik darstellen * * @param title Überschrift * @param legend Array mit Legende * @param data Array mit double-Werten * @return Panel oder null bei Fehler */ public static ChartPanel createPieChart2D(String title, String[] legend, double[] data) { ChartPanel result = null; try { DefaultPieDataset pieDataset = new DefaultPieDataset(); for(int i = 0; i < data.length; i++) { pieDataset.setValue(legend[i], new Double(data[i])); } JFreeChart chart = ChartFactory.createPieChart(title, pieDataset, true, true, false); result = new ChartPanel(chart); } catch(Exception e) { e.printStackTrace(); } return result; }

Grafik, Multimedia

/** * Die Daten als * * @param title * @param legend * @param data * @return */

3D-Tortengrafik darstellen Überschrift Array mit Legende Array mit double-Werten Panel oder null bei Fehler

Listing 218: Chart.java – Hilfsklasse zum Erstellen verschiedener Diagrammtypen (Forts.)

>> Grafik und Multimedia

477

public ChartPanel createPieChart3D(String title, String legend[], double[] data) { ChartPanel result = null; try { DefaultPieDataset pieDataset = new DefaultPieDataset(); for(int i = 0; i < data.length; i++) { pieDataset.setValue(legend[i], new Double(data[i])); }

JFreeChart chart = ChartFactory.createPieChart3D(title, pieDataset, true, true, false); result = new ChartPanel(chart); } catch(Exception e) { e.printStackTrace(); } return result; } /** * Daten als Balkendiagramm darstellen * * @param title Überschrift * @param x_label Beschriftung x-Achse * @param y_label Beschriftung y-Achse * @param legend Array mit Legende * @param data Array mit double-Werten * @return Panel oder null bei Fehler */ public static ChartPanel createBarChart(String title, String x_label, String y_label, String[] legend, double[] data) { ChartPanel result = null; try { DefaultCategoryDataset catDataset = new DefaultCategoryDataset(); for(int i = 0; i < data.length; i++) { catDataset.addValue(data[i], legend[i], ""); } JFreeChart chart = ChartFactory.createBarChart(title, x_label, y_label,catDataset, PlotOrientation.VERTICAL, true, true, false); result = new ChartPanel(chart); } catch(Exception e) { Listing 218: Chart.java – Hilfsklasse zum Erstellen verschiedener Diagrammtypen (Forts.) Grafik, Multimedia

478 >> Torten-, Balken- und X-Y-Diagramme erstellen

e.printStackTrace(); } return result; } /** * x,y Paare als Kurve darstellen; Wertepaare werden automatisch nach * x-Wert aufsteigend sortiert * * @param title Überschrift * @param x_label Beschriftung x-Achse * @param y_label Beschriftung y-Achse * @param data 2-dimensionales Array mit x,y Werten * @return Panel oder null bei Fehler */ public static ChartPanel createXYChart(String title, String x_label, String y_label, double[][] data) { ChartPanel result = null; Grafik, Multimedia try { XYSeriesCollection dataset = new XYSeriesCollection(); XYSeries series = new XYSeries(""); for(int i = 0; i < data.length; i++) { series.add(data[i][0], data[i][1]); } dataset.addSeries(series); JFreeChart chart = ChartFactory.createXYLineChart(title, x_label, y_label, dataset, PlotOrientation.VERTICAL, false, false, false); result = new ChartPanel(chart); } catch(Exception e) { e.printStackTrace(); } return result; } } Listing 218: Chart.java – Hilfsklasse zum Erstellen verschiedener Diagrammtypen (Forts.)

Das Start-Programm zu diesem Rezept erzeugt mittels der Klasse Chart ein Tortendiagramm und einen X-Y-Plot.

>> Grafik und Multimedia

479

import import import import

javax.swing.*; java.awt.*; java.awt.event.*; org.jfree.chart.*;

public class Start extends JFrame { public static void main(String[] args) { Start s = new Start(); s.setSize(400,400); s.setVisible(true); } Start() { setTitle("Chart-Demo"); JPanel panel = new JPanel(); // Tortendiagramm erstellen String[] legend = {"Europa", "Nordamerika", "Südamerika", "Afrika", "Asien"}; double[] data = {38.4, 43.2, 7.0, 5.4, 6.0}; ChartPanel pie2D = Chart.createPieChart2D("Umsatzverteilung", legend, data); panel.add(pie2D);

// X-Y-Plot erstellen double[][] tempData = {{0.0, 0.0},{10.0,0.3},{1.0,0.25},{2.0,0.5}, {3.0,0.4},{4.0,0.6}, {5.0,1.1},{6.0,0.9}, {7.0,0.8},{8.0,0.45},{9.0,0.6},{1.5,0.4}}; ChartPanel plot = Chart.createXYChart("Messwerte", "x-Achse", "y-Achse", tempData); panel.add(plot);

JScrollPane pane = new JScrollPane(panel); add(pane); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } } Listing 219: Erzeugen von Diagrammen mit JFreeChart

Grafik, Multimedia

480 >> Torten-, Balken- und X-Y-Diagramme erstellen

Abbildung 98: Tortendiagramm Grafik, Multimedia Abbildung 99: X-Y-Plot

Reguläre Ausdrücke und Pattern Matching
169 Syntax regulärer Ausdrücke
Reguläre Ausdrücke sind ein mächtiges Werkzeug zur Behandlung von Texten. Im Gegensatz zu herkömmlichen Verfahren basiert es nicht auf einem Zeichen-, sondern auf einem Mustervergleich. Mit Hilfe dieser Muster kann versucht werden, in einem gegebenen Text Entsprechungen (Matches) zu finden. Die regulären Ausdrücke stammen ursprünglich aus einem Bereich, der auf den ersten Blick nicht sehr viel mit Java zu tun zu haben scheint: die Neurobiologie – also die Forschung über die Funktionsweise des Nervensystems. In den fünfziger Jahren des vergangenen Jahrhunderts suchte man Methoden, um Abläufe im Gehirn bei bestimmten Ereignissen und Vorkommnissen mathematisch beschreiben zu können. In den siebziger Jahren wurden diese Ansätze wieder aufgenommen, verfeinert und schließlich in der Suchfunktion des Unix-Editors qed implementiert. Dies war die Geburtsstunde der regulären Ausdrücke in der IT. Die Einsatzbereiche regulärer Ausdrücke sind vielfältig. Sie reichen von verschiedensten Suchfunktionen über die Validierung von E-Mail-Adressen und das Auslesen bestimmter Teile eines Textes bis hin zum Ersetzen von Zeichenketten. Aufgrund ihrer Flexibilität und Leistungsfähigkeit lassen sich mit regulären Ausdrücken Dinge anstellen, für die man sonst einige dutzend oder hundert Zeilen Code benötigt hätte – und das alles in einem Bruchteil der sonst dafür nötigen Zeit. Reguläre Ausdrücke verwenden ihre eigene Syntax, die auf den ersten Blick sehr komplex und abschreckend wirken muss. Bei intensiverer Beschäftigung mit der Syntax erweist sie sich aber als äußerst logisch und nachvollziehbar – nur eben schwer zu lesen. Grundsätzlich ist ein regulärer Ausdruck nichts anderes als ein Textmuster, das sich idealerweise im zu überprüfenden Text identifizieren lässt. Da ein derartiges Muster universeller einsetzbar sein soll als ein einfacher Zeichenkettenvergleich, werden in dem Muster Zeichen und bestimmte Metazeichen kombiniert. Im Anhang finden Sie eine komplette Auflistung aller Metazeichen. Im Folgenden sollen die wichtigsten Metazeichen kurz vorgestellt werden:
Zeichen
x . \\ \0n \n \r \d \D \s

Bedeutung Der Buchstabe x Beliebiges Zeichen Backslash Zeichen mit dem oktalen Wert 0n (0 <= n <= 7) Zeilenumbruch (Line Feed, '\u000A') Carriage-Return ('\u000D') Zahl: [0-9] Nicht-Zahl: [^0-9] Whitespace-Zeichen: [ \t\n\x0B\f\r]

Tabelle 41: Die wichtigsten Metazeichen

RegEx

482 >> Syntax regulärer Ausdrücke

Zeichen
\S \w \W ^ $ \b \B \A \G [abc] [^abc] [a-zA-Z] X? X* X+

Bedeutung Nicht-Whitespace-Zeichen: [^\s] Zeichen, Unterstrich oder Zahl: [a-zA-Z_0-9] Weder Zeichen noch Unterstrich oder Zahl: [^\w] Zeilenanfang Zeilenende Wortgrenze Nicht-Wortgrenze Beginn der Eingabe Ende des vorherigen Treffers a, b oder c Jedes Zeichen außer a, b oder c (Negation) a bis einschließlich z oder A bis einschließlich Z X, ein oder kein Mal X, kein Mal oder mehrmals X, mindestens ein Mal X, genau n Mal X, mindestens n Mal X, mindestens n Mal, aber nicht mehr als m Mal X, gefolgt von Y Entweder X oder Y X wird als Entsprechung gespeichert

RegEx

X{n} X{n,} X{n,m} XY X|Y (X)

Tabelle 41: Die wichtigsten Metazeichen (Forts.)

Neben den Metazeichen können auch verschiedene Flags eingesetzt werden. Eine komplette Auflistung dieser Flags finden Sie im Anhang dieses Buchs, die wichtigsten sollen aber hier zumindest kurz vorgestellt werden:
Flag
Pattern.CASE_INSENSITVE Pattern.MULTILINE

Bedeutung Schaltet die Berücksichtigung der Groß-/Kleinschreibung ein oder aus. Schaltet den Multiline-Modus, bei dem die Symbole ^ und $ auch am Zeilenanfang bzw. -ende matchen (und nicht nur am Anfang oder Ende des kompletten Textes), ein oder aus.

Tabelle 42: Wichtige Flags

>> Reguläre Ausdrücke und Pattern Matching

483

Flag
Pattern.DOTALL

Bedeutung Wenn der DOTALL-Modus aktiviert ist, steht der Platzhalter . für alle Zeichen, inklusive Zeilenumbrüche. Per Voreinstellung werden Zeilenumbrüche nicht als Übereinstimmung gewertet. Schaltet den Literal-Modus ein oder aus, bei dem im Text enthaltene Steuer- oder Metazeichen nicht als solche interpretiert, sondern als gewöhnliche Zeichenketten aufgefasst werden.

Pattern.LITERAL

Tabelle 42: Wichtige Flags (Forts.)

Auf Seiten Javas erfolgt der Einsatz von regulären Ausdrücken meist über eine java.util. regex.Pattern-Instanz, die das eingesetzte Muster kompiliert. Dies beschleunigt die Ausführung des Matchings im Wiederholungsfall deutlich. Der statischen Methode Pattern.compile() wird dabei das zu verwendende Muster übergeben. In einer weiteren Überladung können ebenfalls die anzuwendenden Flags übergeben werden. Die Rückgabe der compile()-Methode ist eine Pattern-Instanz:
Pattern pattern = Pattern.compile(<Muster>);

Diese Pattern-Instanz kann nun verwendet werden, um einen Mustervergleich vorzunehmen. Dabei kommt eine java.util.regex.Matcher-Instanz zum Einsatz, die den Abgleich des Musters mit der zu überprüfenden Zeichenkette vornimmt. Diese Matcher-Instanz wird von der Methode matcher() der instanzierten Pattern-Instanz erzeugt und zurückgegeben:
Matcher matcher = pattern.matcher(<Text>);

Mit Hilfe der so erhaltenen Matcher-Instanz können nun weitere Operationen auf dem untersuchten Text vorgenommen werden. Die Pattern-Klasse stellt jedoch den Ausgangspunkt der Arbeit mit regulären Ausdrücken dar. Ihre wichtigsten Methoden sind:
Methode
static Pattern compile(String regex) static Pattern compile(String regex, int flags)

Beschreibung Kompiliert den als Parameter übergebenen regulären Ausdruck in eine Pattern-Instanz. Kompiliert den als Parameter übergebenen regulären Ausdruck in eine Pattern-Instanz und verwendet dabei die angegebenen Flags. Erzeugt eine Matcher-Instanz, die das gegebene Muster auf den übergebenen Text anwendet. Kompiliert den übergebenen regulären Ausdruck und prüft, ob er auf den übergebenen Text angewendet werden kann. Zerlegt den übergebenen Text anhand des gegebenen Musters.

Matcher matcher(CharSequence input) static boolean matches(String regex, CharSequence input)

String[] split(CharSequence input)

Tabelle 43: Wichtige Methoden der Pattern-Klasse

RegEx

484 >> Überprüfen auf Existenz

Eine Matcher-Instanz erlaubt es, Operationen auf dem Text vorzunehmen. Ihre wichtigsten Methoden sind:
Methode
boolean find() boolean find(int start)

Beschreibung Sucht die nächste Entsprechung des regulären Ausdrucks in der gegebenen Zeichenkette. Setzt den Matcher zurück und sucht nach der nächsten Entsprechung des regulären Ausdrucks beginnend an der durch start angegebenen Position innerhalb der gegebenen Zeichenkette. Gibt die Entsprechung zurück, die durch den vorherigen Match-Prozess gefunden wurde. Gibt die durch group gekennzeichnete Entsprechung zurück, die durch den vorherigen Match-Prozess gefunden wurde. Gibt die Anzahl der gefundenen Gruppen zurück. Gibt an, ob der reguläre Ausdruck auf die Zeichenkette angewendet werden kann. Gibt die Literal-Entsprechung (also mit verdoppelten Backslashes) der übergebenen Zeichenkette zurück. Ersetzt jeden Treffer innerhalb des gegebenen Texts durch die als Parameter angegebene Zeichenkette. Ersetzt den ersten Treffer innerhalb des gegebenen Texts durch die als Parameter angegebene Zeichenkette.

String group() String group(int group)

int groupCount() boolean matches() static String quoteReplacement(String s)

RegEx

String replaceAll(String replacement) String replaceFirst(String replacement)

Tabelle 44: Wichtige Methoden der Matcher-Klasse

170 Überprüfen auf Existenz
Mit Hilfe der statischen Methode matches() der java.util.regex.Pattern-Klasse kann überprüft werden, ob ein Muster überhaupt in einer Zeichenfolge erkannt werden kann:
import java.util.regex.Pattern; public class Start { public static void main(String[] args) { String input = "Default input"; String pattern = "M(ai|ay|ei|ey)e?r"; // Zu testenden Text ermitteln if(args != null && args.length > 0) { input = args[0]; } // Pattern ermitteln Listing 220: Verwendung von Pattern.matches()

>> Reguläre Ausdrücke und Pattern Matching

485

if(args != null && args.length > 1) { pattern = args[1]; } // Ergebnis ausgeben System.out.println( String.format("Pattern \"%s\" does %smatch input \"%s\"", pattern, Pattern.matches(pattern, input) ? "" : "not ", input)); } } Listing 220: Verwendung von Pattern.matches() (Forts.)

Mayr Mayer Meir Meier Meyr Meyer Mair Maier Andere Texte sind nicht gültig. Eigene Muster können mit Hilfe der in Rezept 169 beschriebenen Metasymbole definiert und als zweiter Parameter beim Aufruf übergeben werden.

Abbildung 100: Anwenden verschiedener Muster auf unterschiedliche Zeichenketten

Eine komplette Übersicht über die möglichen Metasymbole finden Sie im Anhang dieses Buchs.

RegEx

Das vom Programm vorgegebene Pattern kann über den zweiten Parameter beim Aufruf des kompilierten Programms von der Kommandozeile aus überschrieben werden. Der erste Parameter repräsentiert die zu überprüfende Zeichenkette. Diese muss gemäß dem Default-Pattern mit dem Buchstaben M beginnen und von den Buchstabenkombinationen ai, ey, ei oder ey gefolgt werden. Anschließend kann optional ein e folgen und am Ende wird der Buchstabe r erwartet. Gültige Texte sind also:

486 >> Alle Treffer zurückgeben

171 Alle Treffer zurückgeben
Wollen Sie alle Treffer eines regulären Ausdrucks in einem Text ausgeben, verwenden Sie eine java.util.regex.Matcher-Instanz und nutzen deren Methode find(), um über die einzelnen Treffer zu iterieren. Die Methode group() gibt den jeweiligen Treffer als String zurück:
import java.util.ArrayList; import java.util.regex.Pattern; import java.util.regex.Matcher; public class FindAll { /** * Gibt alle Treffer eines regulären Ausdrucks zurück */ public static String[] find(String input, String pattern) { ArrayList<String> result = new ArrayList<String>(); // Pattern-Instanz erzeugen Pattern compiled = Pattern.compile(pattern); // Matcher-Instanz erzeugen Matcher matcher = compiled.matcher(input); // Alle Ergebnisse auslesen while(matcher.find()) { result.add(matcher.group()); } // Inhalt in String-Array überführen String[] res = new String[result.size()]; result.toArray(res); // Ergebnis zurückgeben return res; } } Listing 221: Rückgabe aller Treffer eines regulären Ausdrucks

RegEx

Die Methode find() erwartet die Angabe zweier Parameter: des zu überprüfenden Texts und des zu verwendenden regulären Ausdrucks. Soll etwa eine Prüfung analog zum letzten Beispiel auf die verschiedenen Schreibweisen des Namens Meier vorgenommen werden, kann folgender Code verwendet werden:
public class Start { public static void main(String[] args) { // Muster definieren String pattern = "M(ai|ay|ei|ey)e?r"; Listing 222: Ausgabe aller Meier-Abwandlungen in einem String

>> Reguläre Ausdrücke und Pattern Matching 487 // Text einlesen String input = (args != null && args.println(String.Matcher. Die Ausgabe aller Treffer erfolgt in Form einer Liste. } } } Listing 222: Ausgabe aller Meier-Abwandlungen in einem String (Forts. pattern).length)).util. Listing 223: Ersetzen von Mustern per Matcher. Beim Aufruf von der Kommandozeile aus sollte als Parameter die zu überprüfende Zeichenkette übergeben werden.println(String.regex. String pattern.) Abbildung 101: Ausgabe aller Treffer eines regulären Ausdrucks 172 Mit regulären Ausdrücken in Strings ersetzen Die Methode replaceAll() der java.regex.format("%d Treffer:".format(".length > 0 ? args[0] : "Default input"). for(String match : matches) { System. .Pattern. match)).util.out. matches. String replacement) { String result = input. import java.replaceAll() RegEx Der reguläre Ausdruck entspricht dem in Rezept 170 verwendeten Ausdruck. Teile von Zeichenketten anhand von regulären Ausdrücken zu ersetzen: import java.util.out. // Treffer ausgeben System.Matcher-Klasse erlaubt es.find(input.%s".regex. public class ReplaceAll { /** * Ersetzt das Muster in der gegegeben Zeichenkette */ public static String replace( String input. // Treffer auslesen String[] matches = FindAll.

regex.length > 1 ? args[1] : "Default replacement"). // Muster einlesen String pattern = (args != null && args.out.replaceAll(replacement).) Die hier dargestellte statische Methode replace() erwartet die Angabe der Parameter für die Quellzeichenkette. // Einzusetzende Zeichenfolge einlesen String replacement = (args != null && args.488 >> Anhand von regulären Ausdrücken zerlegen // Pattern kompilieren Pattern compiledPattern = Pattern. Diese können beispielsweise von der Kommandozeile eingelesen werden. dann alle Vorkommen ersetzen result = matcher.length > 0 ? args[0] : "Default input"). Die Rückgabe ist ein String-Array. den regulären Ausdruck und die Ersetzung. // Ergebnis zurückgeben return result.replace(input. } } Listing 224: Einlesen der Parameter für die Ersetzung über die Kommandozeile 173 Anhand von regulären Ausdrücken zerlegen Die Methode split() der java. Als Argument wird nur die zu untersuchende Zeichenkette erwartet. wie folgendes Listing zeigt: RegEx public class Start { public static void main(String[] args) { // Zu ersetzende Zeichenfolge einlesen String input = (args != null && args.println( ReplaceAll.replaceAll() (Forts. // Wenn Muster gefunden. // Ersetzung durchführen und Ergebnis ausgeben System. replacement)). } } Listing 223: Ersetzen von Mustern per Matcher.util.compile(pattern). pattern.matcher(input). // Matcher instanzieren Matcher matcher = compiledPattern.length > 2 ? args[2] : "M(ai|ay|ei|ey)e?r").Pattern-Klasse kann Zeichenketten anhand von regulären Ausdrücken zerlegen. das die gefundenen Teile der Zeichenkette ohne das durch den regulären Ausdruck bezeichnete Token beinhaltet: .

regex. können beispielsweise von der Kommandozeile eingelesen werden – wie es hier demonstriert wird: .util. public class Split { /** * Zerlegt eine Zeichenkette anhand des angebenen Tokens */ public static String[] split(String input. token). // Rückgabe ausgeben System. anhand dessen die Zerlegung stattfinden soll.>> Reguläre Ausdrücke und Pattern Matching 489 import java.length > 0) { input = args[0].length > 1) { token = args[1]. } } } Listing 226: Einlesen von Text und Token von der Kommandozeile RegEx Die beiden Parameter für die zu untersuchende Zeichenkette und das Token. part)). parts.split(input. } // Eingabe zerlegen String parts[] = Split.split() public class Start { public static void main(String[] args) { // Standardtext und Token definieren String input = "Default input".out.format(". for(String part : parts) { System.println(String.println(String. // Anhand des übergebenen Musters zerlegen return pattern.Pattern. } // Kommandozeilen-Parameter einlesen: Token if(null != args && args. String token) { // Pattern-Instanz referenzieren Pattern pattern = Pattern.length)).%s". // Kommandozeilen-Parameter einlesen: Text if(null != args && args.split(input).format("%d Teile gefunden". } } Listing 225: Zerlegen einer Zeichenkette mittels Pattern.out.compile(token). String token = " ".

Eine dieser Möglichkeiten stellt die Verwendung regulärer Ausdrücke dar. dass eingegebene oder übergebene Zahlen einem bestimmten Muster entsprechen. 174 Auf Zahlen prüfen Es gibt verschiedene Möglichkeiten. Die beiden Methoden validateInteger() und validateDouble() zur Validierung von Integerund Double-Werten sind zusätzlich in der Klasse implementiert. anhand dessen eine Zerlegung durchgeführt werden soll. Um zum Beispiel anhand von Leerzeichen.Pattern-Instanz erzeugt und mit Hilfe der Methode matches() der referenzierten java. wird der Text »Default input« anhand des Leerzeichens in zwei Teile zerlegt. sollten Sie folgendes Muster verwenden: RegEx |.util. |! Abbildung 103: Zerlegen eines benutzerdefinierten Textes anhand eines anderen Musters Analog verfahren Sie. wird das Leerzeichen definiert. Abbildung 102: Zerlegung mit dem Standardtext und dem Standard-Token Wenn Sie eine Zerlegung einer Zeichenkette anhand eines anderen Musters vornehmen wollen. Zu diesem Zweck wird eine java.parse() .Matcher-Instanz bestimmt. ob das Muster auf den übergebenen Wert angewendet werden kann. Sollte die Klasse also ohne Parameter aufgerufen werden. auf Zahlen zu prüfen. wenn Sie Inhalte anhand von Buchstaben oder kompletten Wörtern zerlegen wollen.regex. Komma mit nachfolgendem Leerzeichen oder Ausrufezeichen eine Zerlegung vorzunehmen. regex. Diese Prüfung wird innerhalb der Methode validate() vorgenommen. müssen Sie dieses Muster als zweiten Parameter sowohl beim Aufruf der Konsolenanwendung als auch beim Aufruf der Methode split() der Split-Klasse angeben. util. die sicherstellen können. Im Falle von Double-Werten sind hier noch weitere Prüfungen nötig – zumindest sollte mit Hilfe von DecimalValue.490 >> Auf Zahlen prüfen Als Standard-Token.

public class ValidateDigit { /** * Prüft auf eine Integer-Zahl */ public static boolean validateInteger(String input) { // Ganze Zahl validieren. Mehr als ein Komma oder Punkt ist nicht zulässig. "^[1-9][0-9]*?$"). führende Null nicht // zulässig boolean result = validate(input.]?[0-9]+?)$").Pattern. ^[1-9]{1}[0-9]*?(?:[.\\. dann casten if(result) { // Versuchen.regex.>> Reguläre Ausdrücke und Pattern Matching 491 sichergestellt werden.Matcher. Die Zeichenkette muss mit genau einer Ziffer zwischen 1 und 9 beginnen. Falls ein Komma oder ein Punkt eingefügt wird.util. um beispielsweise Kommata korrekt zu erkennen. "^[1-9]{1}[0-9]*?(?:[. Die verwendeten regulären Ausdrücke können auf alle Zeichenketten angewendet werden. die folgende Bedingungen erfüllen: Ausdruck ^[1-9][0-9]*?$ Beschreibung Die Zeichenkette muss mit einer Ziffer zwischen 1 und 9 beginnen. muss danach mindestens eine Ziffer folgen.]?[0-9]+?)$ Tabelle 45: Durch reguläre Ausdrücke definierte Bedingungen RegEx Im Code werden die beiden Ausdrücke in den Methoden validateInteger() und validateDouble() verwendet: import java. dass die übergebene Zahl in einen numerischen Wert umwandelbar ist.\\. Anschließend können beliebig viele Ziffern folgen. Anschließend können beliebig viele Ziffern folgen. // Wenn Prüfung erfolgreich.regex. } /** * Prüft auf einen Double-Wert */ public static boolean validateDouble(String input) { // Double-Zahl mit Komma validieren. die Zahl zu casten try { Listing 227: Validierung von Zahlen . import java.util. führende Null nicht zulässig return validate(input. Wesentlich dabei ist die Angabe einer Locale-Instanz.

} catch (ParseException e) { // Zahl konnte nicht gecastet werden result = false.) RegEx Es bietet sich an.length > 1 && args[1]. // Ergebnis zurückgeben return matcher. die Methoden der Klasse ValidateDigit zur Validierung von Integer. } Listing 228: Einlesen der zu prüfenden Zahl und des zu verwendenden Algorithmus . String p) { // Pattern erzeugen Pattern pattern = Pattern. Letzteres ist in der Klasse Start implementiert.getInstance(new Locale("de")). // Matcher instanzieren Matcher matcher = pattern.parse(input). // Übergebene Zahl einlesen if(null != args && args. in deren statischer main()-Methode die zu überprüfende Zahl eingelesen wird (erster Parameter).oder Double-Werten aus anderen Anwendungen oder von der Kommandozeile aus zu verwenden.compile(p). } /** * Prüfen eines Wertes */ public static boolean validate(String input.matches().492 >> Auf Zahlen prüfen DecimalFormat.length > 0) { digit = args[0]. ob auf Double geprüft werden soll if(null != args && args.matcher(input). welche Art der Prüfung vorgenommen werden soll – entweder auf eine ganze Zahl (kein zweiter Parameter) oder auf einen Double-Wert (zweiter Parameter muss »-d« sein): public class Start { public static void main(String[] args) { boolean validateInt = true.equals("-d")) { validateInt = false. Ebenso wird hier evaluiert. } } return result. } // Überprüfen. } } Listing 227: Validierung von Zahlen (Forts. String digit = "123".

Unterstrich. erschließt sich jedoch recht leicht.validateDouble(digit)). Nach dem @-Symbol müssen sich mindestens eine Ziffer. die folgende Bedingungen erfüllen: Die Zeichenkette muss mit mindestens einem Buchstaben oder einer Ziffer beginnen.])+[a-zA-Z]{2. Danach muss ein Punkt folgen.6}$ Dieser Ausdruck kann auf alle Zeichenketten angewendet werden.println( validateInt ? ValidateDigit._+&])*[0-9a-zA-Z]+@([-0-9a-zA-Z]+[\. Es muss ein @-Symbol vorkommen.>> Reguläre Ausdrücke und Pattern Matching 493 // Prüfung durchführen und Ergebnis zurückgeben System. Abbildung 104: Prüfung verschiedener Zahlen 175 E-Mail-Adressen auf Gültigkeit prüfen Gerade E-Mail-Adressen sind aufgrund ihrer potenziellen Komplexität ein dankbares Feld für die Prüfung per regulärem Ausdruck. Anschließend können Bindestrich. Punkt. Plus-Symbol und kaufmännisches Und folgen. Vor dem @-Symbol müssen ein Buchstabe oder eine Zahl stehen.) Das Ergebnis dieser Prüfung wird anschließend ausgegeben. wenn es von links nach rechts gelesen wird: ^([0-9a-zA-Z]+[-\. Das zu verwendende Muster ist zwar nicht unbedingt ein Musterbeispiel für einen einfach zu erfassenden regulären Ausdruck. } } Listing 228: Einlesen der zu prüfenden Zahl und des zu verwendenden Algorithmus (Forts. ein Buchstabe oder ein Bindestrich anschließen.validateInteger(digit) : ValidateDigit. RegEx .out. Beide Bedingungen können sich beliebig oft wiederholen oder auch überhaupt nicht erfüllt werden.

Matcher. Email() statt: Die Prüfung auf die Erfüllung dieser Anforderungen findet innerhalb der Methode validate- import java.matches(). email.util.regex.6}$"). // Ergebnis zurückgeben return matcher. } // Ergebnis ausgeben System. public class EmailValidator { /** * Überprüft eine E-Mail-Adresse auf Gültigkeit */ public static boolean validateEmail(String email ) { // Pattern instanzieren Pattern pattern = Pattern.format("Die E-Mail-Adresse %s ist %sgueltig".Pattern. Am Ende muss eine Toplevel-Domain-Angabe von zwei bis sechs Zeichen Länge folgen.out. if(null != args && args. // Matcher instanzieren Matcher matcher = pattern.matcher(email).validateEmail(email) ? "" : "un")). } } Listing 229: Überprüfung einer E-Mail-Adresse RegEx Das Start-Programm zu diesem Rezept prüft mit Hilfe dieser Klasse über die Kommandozeile übergebene E-Mail-Adressen: public class Start { public static void main(String[] args) { // E-Mail-Adresse einlesen String email = "test".])+[a-zA-Z]{2.494 >> E-Mail-Adressen auf Gültigkeit prüfen Dies kann sich beliebig oft wiederholen.regex.util.println( String. } } Listing 230: Überprüfung von E-Mail-Adressen über die Kommandozeile . import java.compile( "^([0-9a-zA-Z]+[-\\._+&])*[0-9a-zA-Z]+@([-0-9a-zA-Z]+" + "[\\. EmailValidator.length > 0) { email = args[0].

H i n we i s Das Ersetzen von HTML-Tags lässt sich am einfachsten mit Hilfe der Matcher-Methode replaceAll() erledigen. diese manuell zu bearbeiten oder mit Hilfe eines regulären Ausdrucks alle HTML-Codes zu entfernen.compile("<[^>]+?>"). Dies ist eine relativ dankbare Aufgabe.>> Reguläre Ausdrücke und Pattern Matching 495 Ein Test mit verschiedenen E-Mail-Adressen zeigt.Pattern.Matcher. die nach mindestens einem anderen Zeichen mit einer schließenden spitzen Klammer enden. weil HTML-Codes einen definierten und gleichbleibenden Aufbau haben.util.regex. würde dies möglicherweise einen zu weiten Bereich umfassen und zu unerwünschten Ergebnissen führen. Für diesen endet ein Match direkt nach der ersten schließenden spitzen Klammer. der mehrere schließende Klammern enthält. Listing 231: Entfernen aller HTML-Tags aus einem Text RegEx . public class HtmlStripper { /** * Entfernt alle HTML-Tags aus einem Text */ public static String stripHTML(String input) { // Pattern-Instanz referenzieren Pattern pattern = Pattern. dass nur gültige Adressen akzeptiert werden. der sich mit Hilfe des folgenden regulären Ausdrucks beschreiben lässt: <[>]+?> Dieser Ausdruck trifft auf alle mit einer öffnenden spitzen Klammer beginnenden Textfragmente zu.regex. die als Parameter die Ersetzung entgegennimmt: import java. Das Fragezeichen nach dem +-Symbol kennzeichnet einen nichtgierigen regulären Ausdruck. sind Sie entweder gezwungen.util. während ein gieriger Ausdruck bis zur letzten schließenden spitzen Klammer matchen würde. um an deren reinen Inhalte zu gelangen. Bei einem Text. import java. Abbildung 105: Ausgabe der Prüfergebnisse 176 HTML-Tags entfernen Wenn Sie Webseiten herunterladen.

sun. Ein java. // Ersetzung durchführen return matcher.matcher(input). Dies geschieht mit Hilfe einer URL-Instanz.MalformedURLException. import java.net.io. die die Adresse der abzurufenden Seite repräsentiert. if(null != args && args.stripHTML(line). String line = null. Zuletzt werden die Inhalte ausgegeben.URL.) Wesentlich aufwändiger als das Entfernen der HTML-Tags ist das Abrufen einer Webseite. liest die Inhalte der Seite ein. import java. try { // URL-Instanz.496 >> HTML-Tags entfernen // Matcher-Instanz referenzieren Matcher matcher = pattern. die auf einen java. die die gewünschte Webseite // repräsentiert String address = "http://java.BufferedInputStream zugreift. der eine java. Anschließend werden die HTML-kodierten Leerzeichen ebenfalls entfernt.io.replaceAll("").BufferedReader.com". public class Start { public static void main(String[] args) { BufferedReader br = null. } URL url = new URL(address).openStream()))).InputStreamReader-Instanz kapselt.io. // Einlesen der Daten per BufferedReader br = new BufferedReader( new InputStreamReader( new BufferedInputStream(url.readLine())) { // Entfernen aller HTML-Tags aus der Zeile String replacedLine = HtmlStripper. } } Listing 231: Entfernen aller HTML-Tags aus einem Text (Forts. // Inhalt lesen while(null != (line = br. RegEx Listing 232: Abrufen einer Webseite und Entfernen der HTML-Tags . StringBuilder content = new StringBuilder().io.*.length > 0) { address = args[0].net. Jede einzelne eingelesene Zeile wird während des Einlesens durch die Methode stripHTML() von den HTML-Tags befreit. import java.

printStackTrace().com ohne HTML-Tags RegEx . " ").close().".trim(). } } // Ergebnis ausgeben System. } catch (IOException e) { e. "&nbsp. } finally { // Aufräumen if(null != br) { try { br. Abbildung 106: Darstellung von java.printStackTrace().println(content.length() > 0) { content. } } } } } Listing 232: Abrufen einer Webseite und Entfernen der HTML-Tags (Forts.sun.replace( replacedLine.toString()).) Eine Webseite wie http://java. dann // Zeile hinzufügen if(replacedLine.out.com wird so bei Ausführung der Konsolenanwendung auf ihren reinen Text reduziert.sun. } catch (MalformedURLException e) e.>> Reguläre Ausdrücke und Pattern Matching 497 // Entfernen der HTML-Entities für Leerzeichen replacedLine = ReplaceAll. // Wenn mindestens ein Zeichen noch vorhanden. } catch (IOException e) { e.printStackTrace().append(replacedLine + "\r\n").

2}\\)\\s(\\d{1.2}(\\s\\d{1. denn sie validieren nur die Syntax von Zeichenketten. Das Format von Telefonnummern kann jedoch variieren. dass die Vorwahl in Klammern zu setzen ist und sämtliche Ziffern in Gruppen zu je zwei Ziffern geschrieben werden müssen. 05. Aus diesem Grund sind Backslashes auch verdoppelt. Eine Durchwahl ist durch einen Bindestrich vom Rest der Telefonnummer zu trennen: ^\\(\\d{1. Dieser reguläre Ausdruck prüft eine Telefonnummer auf die Einhaltung der deutschen DIN 5008 für die Formatierung von Telefonnummern. von denen einzelne nicht vergeben sind. dass er zwar viele Einsatzzwecke abdeckt.})((-(\\d{1. Wollen Sie diese Ausdrücke über die Kommandozeile (siehe Rezept 170) verwenden. jedoch nie auf alle möglichen und zulässigen Schreibweisen eingehen kann.1})$ Dieser reguläre Ausdruck kann auf diese Telefonnummern angewendet werden: (0 30) 12 34 45 (0 30) 12 34 56 78 (0 30) 12 34 56 7 – 12 Er trifft jedoch nicht auf diese Telefonnummern zu: (030) 12 34 456 +49 (0)30 12 34 56 78 (0 30) 12 34 56 78 – 12334 . An die ersten beiden Ziffern kann sich eine beliebige dreistellige Ziffer anschließen. Österreichische und schweizerische Postleitzahlen sind leichter zu prüfen als ihre deutschen Pendants.4})){0. die im Wesentlichen festlegt. damit keine ungültigen Escape-Sequenzen erzeugt werden. wie sie direkt in Java verwendet werden können. 43 und 62 werden deshalb komplett übergangen. Die folgenden Beispiele stellen reguläre Ausdrücke dar. Die Nummernbereiche 00.2}(\\s\\d{1. wobei die erste Zahl keine Null sein darf: ^[1-9]\\d{3}$ Auf Telefonnummer prüfen Eine Prüfung auf eine Telefonnummer kann im einfachsten Fall der Prüfung auf ganze Zahlen entsprechen. denn sie sind nur vierstellig und fortlaufend nummeriert. nicht deren Inhalt.498 >> RegEx für verschiedene Daten 177 RegEx für verschiedene Daten Bei jedem der folgenden regulären Ausdrücke gilt es zu beachten.2}){1.2}){1. A c htu n g RegEx Auf PLZ prüfen Die Prüfung auf eine deutsche Postleitzahl kann mit Hilfe des folgenden regulären Ausdrucks erfolgen: ^(?:(?:0[1-46-9])|(?:[1-357-9]\\d{1})|(?:4[0-24-9])|(?:6[013-9]))\\d{3}$ Dieser Ausdruck orientiert sich an den Nummernbereichen von Postleitzahlen. Ebenso sollten reguläre Ausdrücke nie als alleinige Kontrollinstanz dienen. Der reguläre Ausdruck muss also auf Zahlen prüfen. müssen Sie die verdoppelten Backslashes (\\) in einfache Backslashes (\) umwandeln. so dass mit einer derartig simplen Prüfung möglicherweise mehr Fehler verursacht als vermieden werden.

>> Reguläre Ausdrücke und Pattern Matching 499 Auf Web.com/index\html http://user:pass@java. %$#_ enthalten.234. die als Tausendertrennzeichen einen Punkt enthalten und die Nachkommastellen durch ein Komma abtrennen – also der deutschen Schreibweise entsprechen.3}(\\.und FTP-Adresse prüfen Um auf Web.com RegEx Folgende Angaben entsprechen hingegen nicht dem regulären Ausdruck: Auf Währungsangaben prüfen Auch die Prüfung auf Währungsangaben kann per regulärem Ausdruck erfolgen.(\\d){3})*)|\\d*)(.'/ +=&amp.). Diese Komponente ist optional. Folgender Ausdruck überprüft auf Währungsangaben.'+=&amp. können Sie diesen Ausdruck verwenden: ^(ht|f)tp(s?)://[a-zA-Z0-9-\\.de java.sun.com/ http:// java.com/index.56€ 1.com http://java. Am Ende darf auch ein €-Zeichen folgen: ^\\s*-?((\\d{1.234. https:// oder ftp://.2})?\\s?(\\u20AC)?\\s*$ Dieser Ausdruck findet folgende Zeichenketten: 123 123.sun.html?a=b ftp://pearson.sun.4 123.sun. Die Pfadkomponente darf die Buchstaben und Ziffern sowie die Zeichen -. Anschließend erfolgt die Angabe des Domain-Namens und einer Top-Level-Domain (.56 € 1. . Dieser reguläre Ausdruck findet folgende Adressangaben: http://java.234.}(/?)([a-zA-Z0-9-\\.234.\\?. damit die Pfadkomponente angehängt werden kann.234.45 1.[a-zA-Z0-9-\\.\\d{1.com/index.sun.com etc.%$#_]*)?$ Er trifft auf alle Eingaben zu.56 € Nicht gefunden werden diese Zeichenketten: o1.56 $ 1. die folgenden Konventionen entsprechen: Sie beginnen mit http://.567 Kreditkartennummer überprüfen Die Verifikation von Kreditkartennummern kann mit Hilfe des folgenden regulären Ausdrucks erfolgen: (^(4|5)\\d{3}-?\\d{4}-?\\d{4}-?\\d{4}|(4|5)\\d{15})|(^(6011)-?\\d{4}-?\\d{4}?\\d{4}|(6011)-?\\d{12})|(^((3\\d{3}))-\\d{6}-\\d{5}|^((3\\d{14}))) . Danach kann ein Slash folgen.html http:// java.sun._]+(\\.sun.und FTP-Adressen zu prüfen.?.de._]+){2.com http:// java.

wenn man es auf herkömmliche Art und Weise machen möchte. angewendet werden. diese Überprüfung mit Hilfe eines Musters vorzunehmen: (\\b\\w+\\b)\\s+([\\w\\W]*?)\\1 Dieser Ausdruck trifft auf alle Zeichenketten zu. getrennt durch Bindestriche oder beginnt mit 6011. Visa. Mastercard). DELETE. gefolgt von einem optionalen Bindestrich und fünf Ziffern oder beginnt mit 3. Beginnt mit 3. der in ein SQL-Statement eingefügt werden soll. Dieser Ausdruck ist Ausdruck doppelt. gefolgt von zwölf Ziffern in Vierergruppen. gefolgt von einem optionalen Bindestrich und sechs Ziffern. gefolgt von zwölf Ziffern. Einen weitaus wirksameren Schutz vor SQL-Injection stellt allerdings die Verwendung von PreparedStatements dar (siehe Rezept 182). können Sie folgenden Ausdruck verwenden: (\\b\\w+\\b)\\s+\\1 Diese Zeichenkette wird erfolgreich getestet: Dieser Ausdruck ist ist doppelt. sondern ausschließlich deren korrektes Format überprüft: Beginnt mit 4 oder 5. Reguläre Ausdrücke erlauben es. INSERT. Beginnt mit 6011. gefolgt von drei Ziffern. in denen ein beliebiges Wort an beliebiger Stelle doppelt vorkommt: Dieser Ausdruck ist ist doppelt. UPDATE. Dabei wird jedoch nicht die Gültigkeit der Nummern. gefolgt von vierzehn Ziffern. REVOKE oder UNION herausfiltert. indem er Schlüsselwörter wie SELECT. . gefolgt von fünfzehn Ziffern. Die Einschleusung von Hochkommata sollte ebenfalls verhindert werden: RegEx (%3c)|(%3e)|(SELECT) |(UPDATE) |(INSERT) |(DELETE) |(GRANT) |(REVOKE) |(UNION) Dieser reguläre Ausdruck sollte am besten mit der Methode replaceAll() einer Matcher-Instanz eingesetzt und auf jeden Wert. Diese Zeichenkette dagegen nicht: Dieser Ausdruck ist Ausdruck doppelt.500 >> RegEx für verschiedene Daten Dieser reguläre Ausdruck prüft auf die Nummern gängiger Kreditkarten (Amex. Wollen Sie nur auf hintereinander stehende Wortverdopplungen prüfen. Wortverdoppelungen verhindern Das Herausfiltern doppelter Worte kann aufwändig werden. einem Bindestrich und zwölf Ziffern in Vierergruppen durch Bindestriche getrennt oder beginnt mit 4 oder 5. SQL-Injection verhindern Ein regulärer Ausdruck kann SQL-Injection-Attacken unterbinden. gefolgt von drei Ziffern. GRANT.

class DatabaseUtil { /** * Verbindungsaufbau zu Oracle-Datenbank mit Thinclient-Treiber * * @param server Servername/IP * @param port Portnummer * @param serviceName Oracle Service Name * @param user Oracle Username * @param password Oracle User Passwort * @return Connection-Objekt oder null */ public static Connection makeOracleConnection(String server. try { // Oracle Treiber laden DriverManager. mit dem Oracle-JDBC-Thinclient sowie der populären MySQL-Datenbank: import java.getConnection() übergeben werden (das genaue Format ist leider datenbankabhängig).printStackTrace().jdbc.registerDriver (new oracle.getConnection(str. Dann erfolgt das eigentliche Verbinden zur Datenbank durch Erzeugen eines Connection-Objekts. } Listing 233: Methoden zur Verbindung mit Oracle.sql.bzw. // Verbindung herstellen String str = "jdbc:oracle:thin:@" + server + ":" + port + ":" + serviceName.OracleDriver()). conn = DriverManager. password). } return conn. String port. Hierzu muss ein JDBC-URL im Format jdbc:TreiberIdentfifer:Name an die Methode DriverManager. String serviceName.driver.*. String user. üblicherweise mit der statischen Methode DriverManager.Datenbanken 178 Datenbankverbindung herstellen Das Aufbauen einer Verbindung zu einer Datenbank besteht aus zwei Aspekten. MySQL-Datenbanken Datenbanken . Das folgende Beispiel zeigt das Vorgehen im Falle einer Oracle-Datenbank.sql. user. Zunächst muss ein geeigneter JDBC-Treiber geladen werden. String password) { Connection conn = null. } catch(SQLException e) { e.registerDriver() aus dem Paket java.

else System. MySQL-Datenbanken (Forts.mysql.502 >> Datenbankverbindung herstellen /** * Verbindungsaufbau zu MySQL-Datenbank * * @param server Servername/IP * @param port Portnummer * @param database MySQL Datenbankname * @param user MySQL Username * @param password MySQL User Passwort * @return Connection-Objekt oder null */ public static Connection makeMySQLConnection(String String String String Connection conn = null.bzw.1. String port.B. user. Konsultieren Sie hierzu die Handbücher.println("Oracle Verbindung hergestellt"). conn = DriverManager. password).makeOracleConnection() beispielsweise wie folgt aussehen: Der Verbindungsaufbau zu einer Oracle-Datenbank könnte mit Hilfe der Methode DatabaseConnection conn = DatabaseUtil."geheim")."dba".1". } Datenbanken return conn.printStackTrace(). . database.makeOracleConnection("192.out.println("Verbindungsaufbau ist fehlgeschlagen"). Der konkrete Name für die zu ladende Treiberklasse sowie der Aufbau des Verbindungsstrings ist abhängig von Treiber und Datenbankversion. user. password) { try { // Treiber laden DriverManager.out.Driver()). für MySQL 3306. if(conn == null) System."kdb". // Verbindung herstellen String str = "jdbc:mysql://" + server + ":" + port + "/" + database. "1521".getConnection(str. für Oracle 9i die Datei ojdbc14. } catch(SQLException e) { e.) Util. Beachten Sie dabei Folgendes: Das jar-Archiv des Datenbanktreibers (z.registerDriver (new com. server.jar) muss im CLASSPATH aufgeführt sein.jdbc. } } Listing 233: Methoden zur Verbindung mit Oracle. Der Standardport für Oracle-Datenbanken ist meist 1521.168.

B.jdbc. Eine Instanz dieser Klasse wird bei einer typischen Webanwendung mit Hilfe von JNDI ermittelt.sql. Größe des Connection-Pools registriert und veröffentlicht haben1. Bitte lesen Sie die entsprechende Dokumentation. Um Connection-Pooling einzusetzen.*. Aufgrund der Vielzahl an Applikationsservern können wir hier nicht auf diese speziellen Konfigurationsaspekte eingehen. class DatabaseUtil { /** Listing 234: Connection-Pooling 1. Solange man ein überschaubares Programm schreiben muss. um die gebundenen Ressourcen freizugeben. für MySQL: import java. sollte die close()Methode aufgerufen werden. Datenbanken . Die hierzu notwendige Klasse ist leider treiberabhängig. (Hierzu muss der Applikationsserver die Datenbank als Datenquelle mit allen notwendigen Einstellungen wie Username. Servlets auf die Datenbank zugreifen. Die Anwendung auf der anderen Seite hat das Problem. für den Oracle Thinclient ist es OracleConnectionPoolDataSource. Passwort.*. welches das Interface ConnectionPoolDataSource aus dem Paket javax. in Wirklichkeit bleibt sie bestehen und kann beim nächsten Anfordern mit getConnection() wieder ohne zeitaufwendigen Neuaufbau zugeteilt werden.optional. für MySQL Connector heißt sie MysqlConnectionPoolDataSource. z.>> Datenbanken 503 Der Verbindungsaufbau ist recht zeitaufwendig und sollte nur selten durchgeführt werden. Die Verbindung wird dabei jedoch nur virtuell geschlossen. Hier bietet sich der Einsatz eines Connection-Pools an. import javax. wo Dutzende oder vielleicht sogar Hunderte von internen Threads bzw. für eine oder mehrere SQLBefehle genutzt werden und dann sofort durch Aufruf von close() geschlossen werden. muss man ein Objekt haben. ist dieser Tipp auch nicht weiter schwer zu befolgen. ein vorhandenes Connection-Objekt so oft wie möglich wiederzuverwenden (siehe hierzu auch Rezept 179). dass bei einer hohen Anzahl an gleichzeitig gehaltenen Verbindungen alle Datenbankressourcen blockiert werden und die ganze Anwendung »einfriert«.sql. Die Threads/Servlets leben meist nur kurze Zeit und können daher selbst keine Verbindung halten. import com. Aus Programmsicht gibt es hierbei keinen Unterschied zwischen einer solchen logischen/virtuellen Verbindung und einer echten wie in Rezept 178 erhaltenen und der Code muss in keinster Weise umgeschrieben werden. Hierbei handelt es sich um eine begrenzte Menge an echten (physikalischen) Datenbankverbindungen.jdbc2. 179 Connection-Pooling Die im vorigen Abschnitt gezeigte Vorgehensweise zum Anfordern einer Datenbankverbindung ist relativ zeitaufwendig – weswegen einmal erhaltene Connection-Objekte mehrfach verwendet werden sollten (statt für jeden SQL-Befehl eine neue Verbindung aufzubauen). die von einem Programm nach Bedarf mit getConnection() angefordert werden.sql implementiert.) Für einfache (Test-)Zwecke kann man auch eine Instanz direkt anlegen.*. Etwas komplizierter wird es bei Webanwendungen und Ähnlichem. Es ist besser. Wenn ein Connection-Objekt für längere Zeit nicht mehr benötigt wird.mysql.

} } Listing 234: Connection-Pooling (Forts.sql. bei dem über den konfigurierten Ressourcenname die Datenbank als Datenquelle (DataSource) bekannt gemacht wird. import javax. } Datenbanken return source.parseInt(port)).printStackTrace(). source = null. String user. source. import java.*. source. class DatabaseUtil { /** * Liefert eine ConnectionPoolDataSource zu der angegebenen JNDI-Ressource * * @param resource Name der Datenquelle * @return DataSource-Objekt */ Listing 235: Methode für Connection-Pooling via JNDI . try { source = new MysqlConnectionPoolDataSource().*. import javax.naming. source.sql. z.setUser(user). } catch(Exception e) { e. source.setDatabaseName(database).B.) Der Regelfall sieht allerdings wie erwähnt den Weg über JNDI vor. source.setPassword(password).setServerName(server).*.504 >> Connection-Pooling * Liefert eine ConnectionPoolDataSource zu der angegebenen Datenbank * * @param server Datenbankserver * @param port Port * @param database Datenbankname * @param user Username * @param password Password * @return DataSource */ public static DataSource getConnectionPoolDataSource(String server.setPort(Integer. String database. String password) { MysqlConnectionPoolDataSource source. String port.

try { ic = new InitialContext(). UPDATE und DELETE durchführen Für das Durchführen eines SQL-Befehls wird neben einem gültigen Connection-Objekt eine Instanz von Statement benötigt. // conn für Queries einsetzen // . ohne dass dabei ein physikalischer zeitaufwendiger Verbindungsaufbau stattfindet: DataSource ds. } } Listing 235: Methode für Connection-Pooling via JNDI (Forts..>> Datenbanken 505 public static DataSource getConnectionPoolDataSource(String resource ) { InitialContext ic = null.printStackTrace(). } Listing 236: Connection-Pooling via JNDI 180 SQL-Befehle SELECT. INSERT.) Mit Hilfe der zurückgelieferten DataSource-Instanz kann ein Programm dann (virtuell) eine Verbindung aufbauen (getConnection()-Aufruf) und nach Gebrauch sofort wieder mit close() schließen – und dies beliebig oft wiederholen..createStatement() erzeugt werden kann. die mit der statischen Methode Connection. conn. Je nach Art des SQL-Befehls muss dann eine geeignete execute()Methode der Klasse Statement zum Ausführen eingesetzt werden: Datenbanken . try { ds = DatabaseUtil.getConnection(). } catch(Exception e) { e.lookup(resource).getConnectionPoolDataSource("java:comp/env/jdbc/kunden"). } catch (Exception e) { e. } return result.close(). Connection conn = ds. DataSource result = null. result = (DataSource) ic.printStackTrace().

} catch(SQLException e) { e. boolean st = stm. class DatabaseUtil { /** * Ausführen einer SELECT-Query * * @param conn Connection Objekt * @param sql SQL-SELECT Query * @return ResultSet oder null */ public static ResultSet executeSelect(Connection conn. try { Statement stm = conn.executeQuery(String sql) für SELECT-Befehle. INSERT. gefundene Zeilen werden als ResultSet-Objekt zurückgegeben.printStackTrace().506 >> SQL-Befehle SELECT.createStatement(). String sql) { int res = 0. int execute(String sql) für INSERT/UPDATE/DELETE-Befehle. } return res. Listing 237: Hilfsmethoden zum Ausführen von SQL-Befehlen Datenbanken . } /** * Ausführen eines INSERT/UPDATE/DELETE-Befehls * * @param conn Connection Objekt * @param sql SQL-Befehl * @return Anzahl betroffene Zeilen oder -1 bei Fehler */ public static int execute(Connection conn. res = stm. Rückgabe ist die Anzahl der betroffenen Zeilen.createStatement(). UPDATE und DELETE durchführen ResultSet.sql. res = stm.printStackTrace().execute(sql). String sql) { ResultSet res = null.*.executeQuery(sql).getUpdateCount(). } catch(SQLException e) { e. try { Statement stm = conn. Die nachfolgend definierten Hilfsmethoden kapseln die erforderlichen Aufrufe und übernehmen die notwendige Exception-Behandlung: import java.

password) { Ein möglicher Aufruf könnte beispielsweise wie folgt aussehen: // Muster für fiktive Datenbank Connection conn = Database. String port. database. "geheim").de".makeOracleConnection("mein. "dbdba".) server. String serviceName. Statement stm = conn. ResultSet. H inwe is SQL-Befehlsstrings dürfen nicht mit einem Semikolon enden.TYPE_SCROLL_INSENSITIVE. String user.CONCUR_UPDATABLE ). Man kann sich auf zwei Arten behelfen: Entweder führt man zusätzlich eine separate Query mit dem SQL-Befehl SELECT COUNT(*) durch oder man verwendet ein scrollbares ResultSet-Objekt und springt an das Ende und zählt so die Zeilen: Connection conn = . String port. "db". ResultSet result = Database. "1521". also z.server. um die Anzahl der gefundenen Zeilen zu ermitteln.B.. String sql = "SELECT * FROM DEMOTABELLE". "SELECT * FROM kunden WHERE name = 'Meier'"). Datenbanken .. String password) { siehe Rezept 178 } /** * Verbindungsaufbau zu MySQL-Datenbank */ public static Connection makeMySQLConnection(String String String String siehe Rezept 178 } } Listing 237: Hilfsmethoden zum Ausführen von SQL-Befehlen (Forts. Anzahl Treffer für eine SELECT-Query ermitteln Das bei einer SELECT-Query zurückgegebene ResultSet-Objekt bietet keine direkte Möglichkeit.createStatement(ResultSet. } return res. user. } /** * Verbindungsaufbau zu Oracle-Datenbank mit Thinclient-Treiber */ public static Connection makeOracleConnection(String server.executeSelect(conn.>> Datenbanken 507 res = -1.

z.last().und updatefähiges ResultSet erlaubt hat: Statement st = conn.printStackTrace(). // zur letzten Zeile springen int num = rs.CONCUR_UPDATABLE). } catch(Exception e) { e. rs.executeQuery("SELECT name.updateString("name". Mit insertRow() wird die neue Zeile in die Datenbank geschrieben. updateString() oder updateInt(). rs.508 >> Änderungen im ResultSet vornehmen ResultSet rs = stm. man kann sie auch editieren und die Änderung in die Datenbank zurückschreiben. rs.executeQuery("SELECT * FROM DEMOTABELLE"). Wirksam wird eine Änderung allerdings erst durch einen nachfolgenden Aufruf von updateRow(): try { ResultSet rs = st. Wert in Datenfeld ändern Das Durchführen von Änderungen in einem vorhandenen ResultSet-Objekt geschieht immer auf der aktuellen Zeile und erfolgt durch Aufruf einer passenden updateXxx()-Methode. . Welche Update-Methode genau zum Einsatz kommt. dass man bei Anlage des zugrunde liegenden Statement-Objekts ein scroll. Alle Update-Methoden erwarten den Namen oder Spaltenindex – bezogen auf die zugrunde liegende SELECT-Query – und den neuen Wert. die durch moveToInsertRow() angesprungen wird und dann wie im Update-Fall geändert wird.updateRow(). wenn der eingesetzte JDBC-Treiber scrollfähige ResultSet-Objekte unterstützt und die abgefragte Tabelle einen Primärschlüssel definiert hat. Bei Letzterem wirken sich Änderungen an den zugrunde liegenden Zeilen in der Datenbank durch andere Sessions auf die Daten im ResultSet-Objekt aus.B. } Datenbanken Zeilen (Datensätze) einfügen Das Einfügen einer neuen Zeile erfolgt über die so genannte Einfügezeile (InsertRow).createStatement (ResultSet. "Korn-Westfelder"). // erste Zeile bearbeiten rs.first().getRowNum(). Mit moveToCurrentRow() springt man dann zurück zur aktuellen Zeile: H i nwe i s Die obige Vorgehensweise funktioniert nur. vorname from KUNDEN"). Darunter fällt natürlich auch das Hinzufügen oder Löschen von Datensätzen. // zur ersten Zeile springen 181 Änderungen im ResultSet vornehmen Mit einem ResultSet-Objekt kann man nicht nur die einzelnen gefundenen Treffer auslesen.first(). Anstelle von TYPE_SCROLL_INSENSITIVE kann auch TYPE_SCROLL_SENSITIVE verwendet werden. ResultSet. rs.TYPE_SCROLL_INSENSITIVE. Voraussetzung ist allerdings. hängt vom Datentyp der zugrunde liegenden Tabellenspalte ab.

// Zeile mit Werten füllen rs. // Zeile 3 löschen rs. // Zu aktueller Zeile zurückspringen rs. vorname from KUNDEN"). "Hintermoser"). . gefolgt von einem Aufruf deleteRow(): try { ResultSet rs = st. kann man unter Umständen dadurch Zeit sparen..prepareStatement( "select * from kunden where name = ? and vorname = ?"). Hierzu dient die Klasse java. dass man sie vorkompiliert. vorname from KUNDEN").moveToInsertRow().printStackTrace(). Hierbei wird das zeitaufwendige Parsen und Analysieren des SQL-Befehls nur einmal gemacht. sich ändernde Werte durch den Platzhalter ? ersetzt werden.PreparedStatement und die Factory-Methode prepareStatement() von Connection. Ein vorkompilierter SQL-Befehl für die Query select * from kunden where name = 'Meier' and vorname = 'Hugo' würde man beispielsweise wie folgt anlegen: Connection conn = . Als Argumente übernimmt die Methode den SQL-String.absolute(3).updateString("vorname".printStackTrace().updateString("name".moveToCurrentRow(). rs.sql. Beim wiederholten Aufrufen muss man dann lediglich die aktuellen Werte für die gewünschten Parameter einsetzen.>> Datenbanken try { ResultSet rs = st. // auf Zeile 3 positionieren rs.executeQuery("SELECT name. "Kurt"). PreparedStatement ps = conn. wobei konkrete. Datenbanken } catch(Exception e) { e.deleteRow(). // Zeile hinzufügen rs. } 182 PreparedStatements ausführen Wenn immer wieder der gleiche SQL-Befehl ausgeführt werden soll. } 509 Zeilen (Datensätze) löschen Für das Löschen genügt das Positionieren in der gewünschten Zeile..executeQuery("SELECT name. } catch(Exception e) { e.

makeOracleConnection("mein. "1521". so dass wir hier das Vorgehen nur für Oracle-Datenbanken zeigen.h.?. "Meier"). // Aufruf-String String query = "{?= call searchCustomer(?. Für den Aufruf wird ein besonderes Objekt vom Typ java.executeQuery().Types (siehe Tabelle 46) übergeben. setFloat() der Wert gesetzt und bei SELECT-Befehlen mit executeQuery(). dem man ähnlich wie bei PreparedStatement den gewünschten Aufruf übergibt. setString(). sofern ein JDBC 3. "geheim"). ps.setString(1.?)}".sql.server.de". z. Datenbanken Listing 238: Aufruf einer Stored Procedure . per Platzhalter ?. wird nun mit je nach Datentyp passenden setXxx()-Methoden wie setString(). Beispiel: Eine Oracle-Stored Function mit der folgenden Signatur soll aufgerufen werden: searchCustomer(p1 IN varchar2. Wenn es an das Ausführen des SQL-Befehls mit konkreten Werten geht. der als Nummer 1 gezählt wird!) muss der Datentyp gesondert registriert werden – mit Hilfe der Methode registerOutParameter(). p3 OUT int) RETURN VARCHAR2 Diese Function erwartet zwei Eingabeparameter p1 und p2 und liefert ein int-Ergebnis im Parameter p3 zurück sowie einen String als Funktionswert. definiert werden. setInt(). "Hugo"). ResultSet rs = ps.B.setString(2. die innerhalb des Datenbankservers ausgeführt wird und dadurch in der Regel sehr performant sein kann.sql. wobei alle Übergabeparameter (bei Functions auch der Rückgabewert) als Parameter. // Call-Objekt erzeugen CallableStatement st = conn. "db". der Sie neben der Nummer des Parameters die zu dem SQL-Datentyp passende Konstante in java.0-kompatibler Treiber verfügbar ist. p2 IN varchar2. Für die Parameter vom Typ IN werden die zu übergebenden Werte mit Hilfe von entsprechenden setXxx()-Methoden gesetzt. Theoretisch sollte das gezeigte Vorgehen aber bei beliebigen Datenbanken funktionieren. 183 Stored Procedures ausführen Eine Stored Procedure oder Stored Function ist eine Methode.prepareCall(query). try { // siehe Rezept 178 für makeOracleConnection Connection conn = DatabaseUtil.510 >> Stored Procedures ausführen Die Platzhalter werden dabei von links nach rechts mit 1 beginnend gezählt. bei INSERT/UPDATE/DELETE mit execute() angewendet: ps.CallableStatement benötigt. "dbdba". d.1). Für OUT-Parameter (hierzu zählt auch der Funktionsrückgabewert. Leider ist dieses Feature für MySQL erst in zukünftigen Versionen geplant (jetziger Stand: ab Version 5.

"Hugo").sql. // OUT-Parameter p3 st. st.sql.Date java.printStackTrace(). // OUT-Parameter registrieren // Funktionsrückgabewert st.Array long boolean java.sql.registerOutParameter(1. // Funktionswert int num = st.INTEGER).) SQL-Typ ARRAY BIGINT BIT BLOB BOOLEAN CHAR CLOB DATE DECIMAL DOUBLE FLOAT INTEGER – JDBC-SQL-Typ in java.1) Beliebige Binärdaten Boolescher Wert Zeichenkette fester Länge Für große Zeichenketten Datumsangaben Festkommazahl Gleitkommazahl in doppelter Genauigkeit Gleitkommazahl in doppelter Genauigkeit 32 Bit Ganzzahl Speicherung von Java-Objekten Tabelle 46: Typzuordnung zwischen SQL und Java Datenbanken .setString(2.BigDecimal double double int Object Beschreibung SQL-Feld 64 Bit Ganzzahl Einzelnes Bit (0. // ausführen st. } Listing 238: Aufruf einer Stored Procedure (Forts.registerOutParameter(4.execute().Blob boolean String java.getString(1).sql.Clob java.Types. java.setString(3. java. // OUT-Parameter p3 (Nr 4) } catch(Exception e) { e.Types.Types ARRAY BIGINT BIT BLOB BOOLEAN CHAR CLOB DATE DECIMAL DOUBLE FLOAT INTEGER JAVA_OBJECT Java-Typ java.math.getInt(4).sql. "Meier").sql.VARCHAR).sql.>> Datenbanken 511 // Übergabewerte setzen st. // Ergebnis auslesen String status = st.

mit einer BLOB-Spalte für das Titelbild und einer CLOB-Spalte für den Buchtext: import java. kennt MySQL die Typen BLOB (bis 65. MEDIUMBLOB (bis 1. Der folgende Code geht von einer Oracle-Tabelle Buecher aus. für Textdaten einen FileReader. import java. Die genauen Namen der Datentypen sind teilweise abhängig von der Datenbank.6 Mbyte) und LARGEBLOB (bis 4. gehen Sie wie folgt vor: 1. MEDIUMTEXT.und CLOB-Daten SQL-Typ NULL JDBC-SQL-Typ in java. Für Binärdateien verwenden Sie einen FileInputStream. Minuten.BigDecimal float java. Sie lesen die Daten ein. Sie setzen einen SQL-Befehl oder eine PreparedStatement zum Einfügen der Daten auf..) 184 BLOB.Time String Beschreibung Darstellung des NULL-Werts (= kein Wert) Dezimalzahlen mit fester Genauigkeit Gleitkommazahl einfacher Genauigkeit Zeitdarstellung (Stunden.math.*. Abhilfe schaffen die Datentypen BLOB für die Speicherung beliebig großer binärer Daten sowie CLOB für Zeichenketten. . 3.512 >> BLOB.sql.sql.535 Byts).io.sql.2 Gbyte) sowie anstelle von CLOB die Typen TEXT. LONGTEXT (Größen wie bei BLOB-Varianten).und CLOB-Daten Das Speichern von größeren Zeichenketten oder beliebigen Binärdaten ist mit den gängigen Datenbanken und JDBC erstaunlich beschränkt. Sie schicken den SQL-Befehl mit executeUpdate() ab.Types NULL Java-Typ null für Java-Objekte. Listing 239: Schreiben von BLOB-/CLOB-Daten . 0 für numerische Typen java. A cht un g Datenbanken BLOB-/CLOB-Daten in Datenbank schreiben Um BLOB-Daten aus Dateien einzulesen und in eine Datenbank zu schreiben.*. 2. Sekunden) Zeichenketten variabler Länge NUMERIC REAL TIME VARCHAR NUMERIC REAL TIME VARCHAR Tabelle 46: Typzuordnung zwischen SQL und Java (Forts..B. z. false für boolean. da der Datentyp VARCHAR auf 255 Zeichen beschränkt ist (und auch spezifische Datentypen wie VARCHAR2 bei Oracle erlauben nur eine bescheidene Länge von maximal 4096 Zeichen).

das Lesen von BLOB/CLOB-Daten.setString(1.prepareStatement("INSERT into buecher" + " VALUES (?. } catch(Exception e) { e.de". ps.Clob zurückliefern lassen. ?. "1521". (int) text. "db". FileInputStream fisImage = new FileInputStream(img).*.sql.setCharacterStream(3. "geheim").und CLOB-Daten Datenbanken . File text = new File("content. Listing 240: Hilfsmethoden zum Auslesen von BLOB. import java. getClob() einen »Locator« vom Typ java.length()).fisImage.printStackTrace().length()).executeUpdate(). "3645-57876-46565-6").setBinaryStream(2. ?)").Blob bzw. // PreparedStatement aufsetzen PreparedStatement ps = c. // SQL-Befehl abschicken int num = ps. ps. kann über eine normale SELECTQuery erfolgen. } Listing 239: Schreiben von BLOB-/CLOB-Daten (Forts. getSubString() für CLOB-Daten – Sie auf die Daten zugreifen können.>> Datenbanken 513 try { // siehe Rezept 178 für makeOracleConnection Connection c = DatabaseUtil. mit dessen Methoden – getBytes() für BLOB-Daten bzw. // Daten einlesen File img = new File("cover. java. "dbdba". frText.(int) img. Vom ResultSet-Objekt können Sie sich dann mit den Methoden getBlob() bzw.tif"). class DatabaseUtil { /** * Lesen eines BLOB aus der Datenbank * * @param rs ResultSet-Objekt von SELECT-Query * @param num Nummer der Spalte mit dem Blob * @return Array byte[] mit Blobdaten oder null bei Fehler */ public static byte[] readBlob(ResultSet rs.sql.makeOracleConnection("mein.server. ps. int num) { try { Blob b = rs.) BLOB-/CLOB-Daten lesen Die umgekehrte Richtung. FileReader frText = new FileReader(text).getBlob(num).txt").sql.

String password) { siehe Rezept 178 } /** * Verbindungsaufbau zu MySQL-Datenbank */ public static Connection makeMySQLConnection(String String String String siehe Rezept 178 } } Listing 240: Hilfsmethoden zum Auslesen von BLOB.getClob(num). String port.) Datenbanken server. return b.len).length(). } } /** * Lesen eines CLOB aus der Datenbank * * @param rs ResultSet-Objekt von SELECT-Query * @param num Nummer der Spalte mit dem Clob * @return String mit Clobdaten oder null bei Fehler */ public static String readClob(ResultSet rs.514 >> BLOB. } } /** * Verbindungsaufbau zu Oracle-Datenbank mit Thinclient-Treiber */ public static Connection makeOracleConnection(String server. String str = c.und CLOB-Daten (Forts. len).length(). } catch(Exception e) { return null. password) { .getBytes(1. user. int len = (int) c. int num) { try { Clob c = rs. String serviceName. String user.und CLOB-Daten int len = (int) b. String port. } catch(Exception e) { return null. database. return str.getSubString(1.

executeUpdate("INSERT . ImageIcon bookCover = new ImageIcon(imageBytes).createStatement().executeQuery(sql).. "geheim").. Statement stm = conn. // den Buchtext laden String bookText = DatabaseUtil.server. so dass entweder alle erfolgreich ausgeführt werden oder keine. nach jedem einzelnen Befehl wird in der Datenbank ein Commit durchgeführt und die Änderungen sind bleibend. Normalerweise ist eine per JDBC-Treiber geöffnete JDBC-Verbindung im Autocommit-Modus.. ResultSet res = stm. mit einer BLOB-Spalte für das Titelbild und einer CLOB-Spalte für den Buchtext: try { // siehe Rezept 178 für makeOracleConnection Connection conn = DatabaseUtil..rollback(). st. // Datenbankänderungen akzeptieren // oder: conn. if(res. String sql = "SELECT * FROM buch where isbn = '3645-57876-46565-6'".>> Datenbanken 515 Der folgende Code geht von einer Oracle-Tabelle Buecher aus."). 2). // SQL-Befehl durchführen Statement st = conn.h.commit().printStackTrace(). } } catch(Exception e) { e. } Listing 241: Lesen von BLOB-/CLOB-Daten Unter einer Transaktion versteht man die Zusammenfassung von mehreren SQL-Befehlen zu einer logischen Einheit.de". "db".createStatement(). // Transaktion beenden conn.readClob(res.readBlob(res.3).makeOracleConnection("mein.next() == true) { // das Buchcover laden byte[] imageBytes = DatabaseUtil. d. "dbdba"..setAutoCommit(false). siehe Rezept 178 conn. um Änderungen rückgängig zu machen Listing 242: Mit Transaktionen arbeiten Datenbanken 185 Mit Transaktionen arbeiten . "1521". Für Transaktionen muss man daher diesen Automatismus im erhaltenen Connection-Objekt ausschalten und dann an den gewünschten Stellen durch Aufruf der commit()-Methode die bisher abgesetzten SQL-Befehle persistent machen oder mit rollback() wieder rückgängig machen: // AutoCommit ausschalten Connection conn = .

die seit dem letzten commit() an die Datenbank gesendet worden sind. Häufig werden Batch-Operationen als Transaktionen ausgeführt. import java. Datenbanken // SQL Nr 2 // SQL Nr.commit(). // SQL Nr 1 // Savepoint setzen Savepoint sp = conn...createStatement().. st. // Commit conn.rollback(sp). Hierbei werden alle SQL-Befehle gesammelt und in einem Block zur Datenbank geschickt.. DELETE) vorgenommen werden sollen. rollback() betrifft alle SQL-Befehle."). siehe Rezept 178 conn. ob als Transaktion zusammenfassen * @return int-Array mit Anzahl betroffener Zeilen pro * SQL-Befehl oder null bei Fehler Listing 243: Hilfsmethode zur Batch-Ausführung von SQL-Befehlen . was teilweise deutliche Geschwindigkeitsvorteile bringen kann.executeUpdate("INSERT .. Eine etwas genauere Unterteilung bietet der Einsatz von java. z."). Ein Savepoint-Objekt ist eine Markierung innerhalb einer Transaktion und man kann sie an die rollback()-Methode übergeben: Dann werden nur die SQL-Befehle rückgängig gemacht. die nach dem Setzen der Savepoint-Markierung gesendet worden sind. // Weitere SQL-Befehle durchführen st. // SQL-Befehl durchführen Statement st = conn. so dass alle oder keine der SQL-Operationen erfolgreich ist. 3 // SQL Nr 2&3 rückgängig // SQL 1 persistent 186 Batch-Ausführung Wenn viele einzelne Datensatzänderungen (INSERT. st.516 >> Batch-Ausführung Savepoints Der Aufruf von commit() bzw.sql. UPDATE. class DatabaseUtil { /** * Abarbeitung von SQL-Befehlen im Batch-Modus * * @param conn Connection-Objekt * @param sql Array mit SQL-Befehlen * @param asTransaction Angabe.setSavepoint(). kann es (sofern von der Datenbank/vom Treiber unterstützt) sinnvoll sein.executeUpdate("INSERT .Savepoint.setAutoCommit(false). eine so genannte Batch-Ausführung zu verwenden.. // AutoCommit ausschalten Connection conn = ...").sql.B.executeUpdate("UPDATE . // Rollback conn.*.

Savepoint sp = null. } Statement stm = conn. } result = stm.getNextException(). bei Fehler alles rückgängig machen conn. try { if(asTransaction) { autoCommitOld = conn.println(n).getAutoCommit().setAutoCommit(false). } catch(BatchUpdateException bex) { result = null.printStackTrace().setSavepoint(). } try { if(result == null && asTransaction == true) // in transaction modus. boolean asTransaction) { int[] result = null. } } catch(Exception e) { e. else if(result != null && asTransaction == true) conn. // alten Zustand merken conn. SQLException n = bex. String[] sql.rollback(sp). sp = conn.>> Datenbanken 517 */ public static int[] executeBatch(Connection conn. } } catch(Exception e) { e. for(String str : sql) { stm. } Listing 243: Hilfsmethode zur Batch-Ausführung von SQL-Befehlen (Forts.releaseSavepoint(sp). conn.createStatement().commit(). // aufräumen if(asTransaction == true) { conn.) Datenbanken .addBatch(str). result = null. n = n.executeBatch().setAutoCommit(autoCommitOld). boolean autoCommitOld = true. while( n!= null) { System.out.printStackTrace().

executeBatch(conn. sql[0] = "INSERT INTO kunden VALUES(1.) Datenbanken server.de". " + i + ":" + result[i]). // Batch-Ausführung starten int[] result = DatabaseUtil. String port. "db". String password) { siehe Rezept 178 } /** * Verbindungsaufbau zu MySQL-Datenbank */ public static Connection makeMySQLConnection(String String String String siehe Rezept 178 } } Listing 243: Hilfsmethode zur Batch-Ausführung von SQL-Befehlen (Forts. for(int i = 0. In JDBC werden dabei zwei Datengruppen unterschieden: die Datenbank selbst sowie die zurückgelieferten Daten einer SELECT-Query (in Form eines ResultSet-Objekts). . false). } /** * Verbindungsaufbau zu Oracle-Datenbank mit Thinclient-Treiber */ public static Connection makeOracleConnection(String server.518 >> Metadaten ermitteln return result. // SQL-Befehle für Batch-Ausführung sammeln String[] sql = new String[3]. i++) System. password) { Das folgende Codefragment demonstriert die Verwendung: // siehe Rezept 178 für makeOracleConnection Connection conn = DatabaseUtil. String serviceName. sql. "1521".println("Ergebnis SQL Nr.'Peter')". "dbdba". sql[2] = "DELETE FROM kunden WHERE name = 'Schmidt'".server. sql[1] = "INSERT INTO kunden VALUES(2. i < 3. database. 187 Metadaten ermitteln Metadaten sind Informationen über die Struktur von anderen Daten. user. String port. String user.'Kurt')".out.'Meier'.'Müller'. "geheim").makeOracleConnection("mein.

getString(1)). } } } Listing 244: Hilfsmethode zum Ausgeben einiger wichtiger Datenbank-Metadaten Aufgerufen wird die Methode einfach mit dem Connection-Objekt der abzufragenden Datenbank als Argument: Connection conn = .getDatabaseProductVersion()).out..getMetaData(). DatabaseMetaData bietet über 150 Methoden zum Abfragen diverser Informationen ab. Die nachfolgend definierte Methode demonstriert den Zugriff auf die Metadaten und gibt selbst einige grundlegende Informationen wie Datenbankversion und JDBC-Treiber auf die Konsole aus. System.sql.out. System.>> Datenbanken 519 Datenbank-Metadaten Zum Ermitteln von Metadaten über die Datenbank dient die Klasse java.*.getCatalogTerm() + "(s)").DatabaseMetaData. while(cats.next()) { System. import java.printDBMetaData(conn).out. } } catch(Exception e) { e. class DatabaseUtil { /** * Gibt Datenbank-Infos auf Konsole aus * * @param conn Connection-Objekt */ public static void printDBMetaData(Connection conn) { try { DatabaseMetaData md = conn.getDriverVersion()).getUserName() + "\n").out.println("angemeldet als : " + md. ResultSet cats = md.printStackTrace(). System. Datenbanken . von der man eine Instanz über das Connection-Objekt erhalten kann.out.getDatabaseProductName() + " " + md.getDriverName() + " " + md.println("JDBC Treiber : " + md.sql.println("vorhandene " + md.getCatalogs(). // siehe Rezept 178 DatabaseUtil..println("Datenbanktyp : " + md.println(cats. // Achtung: getCatalogTerm() wird nicht von allen Treibern sinnvoll // implementiert! System.

printResultSetMetaData(rs). die Anzahl der Spalten und ihre Datentypen.*. } .printStackTrace()..sql.B.getColumnCount(). for(int i = 1. } catch(Exception e) { e. ResultSet rs = st.println("Anzahl Spalten im ResultSet: " + num). i++) { System. int num = md.printStackTrace().getColumnName(i) + " Datentyp: " + md.getMetaData(). } } } Listing 245: Abfrage von ResultSet-Metadaten Datenbanken Aufgerufen wird die Methode einfach mit dem abzufragenden ResultSet-Objekt als Argument: try { Connection conn = . System.getColumnTypeName(i)). vorname from KUNDEN").println("Spalte " + i + " Name: " + md. i <= num. } } catch(Exception e) { e.out.out.520 >> Metadaten ermitteln ResultSet-Metadaten Über das von einer SELECT-Query zurückgegebene ResultSet-Objekt lassen sich ebenfalls Metadaten ermitteln. class DatabaseUtil { /** * Gibt ResultSet-Infos auf Konsole aus * * @param conn ResultSet-Objekt */ public static void printResultSetMetaData(ResultSet rs) { try { ResultSetMetaData md = rs. // siehe Rezept 178 Statement st = conn. import java. z. DatabaseUtil. Die nachfolgend definierte Methode demonstriert den Zugriff auf die Metadaten und gibt selbst einige grundlegende Informationen wie die Anzahl der Datensätze sowie Spaltennamen und -typen auf die Konsole aus.executeQuery("SELECT name.createStatement()..

// Query durchführen Statement stm = conn. damit der Zugriff wie gewünscht klappt: Sicherheit: Ein Applet darf eine JDBC-Verbindung nur zu seinem Ursprungsserver aufbauen (es sei denn.executeQuery(sql).awt. Das folgende Beispiel zeigt ein einfaches Applet. String String String String String myServer myPort database user password = = = = = null.createStatement( ResultSet.applet.CONCUR_READ_ONLY). ResultSet. "3306". String sql ="SELECT * from mig_status".*. "root". die insbesondere beim Internet Explorer fest eingebaute Virtual Machine kennt kein JDBC. user. myPort. import java. password). public class DatabaseApplet extends Applet { int numRows = 0. int numColumns = 0. Listing 246: Datenbankzugriff via Applet Datenbanken .makeMySQLConnection(myServer. Treiber: Es sollte ein Typ 3 oder Typ 4 sein (keinesfalls ein JDBC-ODBC-Treiber).*.TYPE_SCROLL_INSENSITIVE. "kosamig". die entsprechenden Policy-Dateien der verwendeten Java Runtime wurden entsprechend angepasst).>> Datenbanken 521 188 Datenbankzugriffe vom Applet Im Prinzip gelten für JDBC-Zugriffe aus einem Applet heraus natürlich die gleichen Regeln wie für normale Anwendungen oder auch Servlets.*. Allerdings gibt es – natürlich! – Kleinigkeiten. welches eine Tabelle aus einer MySQLDatenbank ausliest und anzeigt. "root". die man beachten muss. String[][] tabdata. ResultSet rs = stm. import java. database. import java. Browser: Der Browser muss ein aktuelles Java-Plugin installiert haben.getHost(). Connection conn = DatabaseUtil.sql. /** * in start-Methode die Datenbankverbindung herstellen und Daten lesen */ public void start() { try { // Mit Datenbank verbinden myServer = getDocumentBase(). der zusammen mit dem Applet-Code in einem jar-Archiv gebündelt ist.

// Datenbankverbindung schließen conn. col < numColumns.close(). numRows = rs.getRow(). } // Ergebnis ausgeben repaint().) Datenbanken .drawString(output. col++) tabdata[row][col] = rs.getString(col+1). for(int i = 0.beforeFirst(). i++) { output = "". numColumns = meta. } catch(Exception e) { System.next()) { for(int col = 0. } } /** * in paint-Methode Daten ausgeben */ public void paint(Graphics g) { String output. // ResultSet-Daten in String-Array einlesen tabdata = new String[numRows][numColumns]. while(rs.20. rs. j++) output = output + tabdata[i][j] + " ". int row = 0.last().getMetaData().20 + i * 30).err.getColumnCount(). rs. } } } Listing 246: Datenbankzugriff via Applet (Forts. g.522 >> Datenbankzugriffe vom Applet // Anzahl Spalten und Datensätze abfragen ResultSetMetaData meta = rs. for(int j = 0. j < numColumns. row++.println("Exception bei Verbindung " + e). repaint(). i < numRows.

jar" width="600" height="500"> </applet> </body> </html> Listing 247: Einbettung des Applets in Webseite A c h t u ng Beachten Sie hierbei bitte.w3. dass das Archiv DatabaseApplet.class-Datei des obigen Applets.dtd"> <html> <head> <title> Datenbank-Zugriff über Applet </title> </head> <body> <p>Zugriff auf Datenbank-Server</p> <applet code="DatabaseApplet.01//EN" "http://www.jar sowohl die jar-Datei des Datenbanktreibers enthalten muss als auch die .class" archive="DatabaseApplet.org/TR/html4/strict. Datenbanken .>> Datenbanken 523 Die HTML-Seite zum Aufruf: <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.

.

out.getInetAddresses(). import java. public class DisplayInterfaces { /** * Gibt eine Liste aller Netzwerk-Interfaces samt * zugeordneter IP-Adressen aus */ public static void display() { try { // Netzwerk-Interfaces abrufen Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces(). Die Methode getHostName() einer InetAddress-Instanz liefert die IP-Adresse.hasMoreElements()) { InetAddress address = addresses.net.getName(). // Adresse ausgeben System.net.getDisplayName())).nextElement().InetAddress-Instanzen über die Methode getInetAddresses() der NetworkInterface-Instanz abgerufen werden. import java. // Alle Interfaces durchlaufen while(interfaces. } Listing 248: Ausgabe aller Netzwerk-Interfaces samt deren IP-Adressen Netzwerke.nextElement().util. System. // Adressen durchlaufen while(addresses.net.%s".Netzwerke und E-Mail 189 IP-Adressen ermitteln Die Klasse java.net. E-Mail .hasMoreElements()) { // Aktuelles Element abrufen und Namen ausgeben NetworkInterface ni = interfaces. die der Netzwerkverbindung zugeordnet ist.format("Netzwerk-Interface: %s (%s)".NetworkInterface verfügt über eine statische Methode getNetworkInterfaces().Enumeration.NetworkInterface zurückgibt. ni.out. // Adressen abrufen Enumeration<InetAddress> addresses = ni.println( String. address.*. ni. die eine Enumeration vom Typ java. Diese Adressen können in Form einer Enumeration aus java.getHostAddress())).format(".println( String. Jedes Element dieser Enumeration repräsentiert einen Netzwerkadapter samt Anzeigename und IP-Adressen.

Über die explizite Angabe einer java.NetworkInterface-Instanz kann angegeben werden. gilt die Prüfung als fehlgeschlagen.out. gilt die Adresse als erreichbar. in Millisekunden an. int ttl.526 >> Erreichbarkeit überprüfen System.println(). Dabei wird eine ECHO-Request an den jeweiligen Server gesendet. } } catch (SocketException e) { e. welche Netzwerkschnittstelle verwendet werden soll. int timeout) throws IOException Der Parameter timeout gibt die Zeitspanne. Sollen alle Netzwerkschnittstellen verwendet werden.printStackTrace(). E-Mail Abbildung 107: Ausgabe der verfügbaren Netzwerk-Interfaces samt deren zugeordneten IP-Adressen 190 Erreichbarkeit überprüfen Mit Hilfe der Methode isReachable() einer InetAddress-Instanz kann überprüft werden. Die Methode isReachable() ist überladen: boolean isReachable(int timeout) throws IOException boolean isReachable(NetworkInterface netif. ob diese von außen erreichbar ist.) Netzwerke. ist der Wert null zu übergeben.net. } } } Listing 248: Ausgabe aller Netzwerk-Interfaces samt deren IP-Adressen (Forts. wie lange auf eine Antwort vom Server gewartet wird. Wird sie beantwortet. Wird diese Zeitspanne überschritten. .

ob ein Host per ECHO-Request erreichbar ist Netzwerke. Es kann eine IOException geworfen werden. Die Verwendung der beschriebenen Methode aus eigenen Klassen heraus ist sehr simpel: Die statische Methode CheckAvailability. } } Listing 249: Überprüfung. ob eine IP-Adresse erreichbar ist */ public static boolean isReachable( String host.io. Selbiges gilt für die Angabe eines negativen Werts für das Timeout der Anfrage. ob Adresse erreichbar ist if(null != address) { return address.InetAddress. } catch (UnknownHostException e) { // Wird nicht speziell behandelt } // Überprüfen.>> Netzwerke und E-Mail 527 Die maximale Anzahl an Hops wird über den Parameter ttl definiert. Wird eine negative Anzahl an Hops übergeben. Der Standardwert ist hier null (= unendlich). Dies ist ein starkes Indiz für eine nicht existente Internetverbindung oder einen falsch geschriebenen Hostnamen.getByName() kann eine UnknownHostException geworfen werden. } // Default-Rückgabe return false. wenn Netzwerkprobleme auftreten. wird eine IllegalArgumentException ausgeworfen.net.net. die aufgefangen oder deklariert werden muss: .getByName(host). E-Mail Beim Erzeugen einer InetAddress-Instanz mit Hilfe der statischen Methode InetAddress. import java.isReachable(timeout). wenn der Hostname nicht in eine IP-Adresse aufgelöst werden kann. public class CheckAvailability { /** * Prüft. try { address = InetAddress. Diese Ausnahme ist entweder zu deklarieren oder abzufangen: import java.IOException. import java.UnknownHostException.isReachable() nimmt als Parameter den Hostnamen und das Timeout in Millisekunden entgegen. int timeout) throws IOException { // InetAddress-Instanz erzeugen InetAddress address = null. Bei Verwendung der Methode isReachable() kann es zu IOExceptions kommen.

if(args. host.length > 0) { // Host ermitteln String host = args[0].". // Standard-Timeout festlegen int timeout = 4000. reachable ? "" : "not ")).IOException.isReachable(host.length() > 0 && !to.format("Host %s is %3$sreachable in %2$d MilliSeconds. // In Integer casten if(to.println( String.length > 1) { // Versuchen. timeout). } catch (NumberFormatException e) { // Wird nicht speziell behandelt } } } // Ergebnis-Variable definieren boolean reachable = false.parseInt(to).io. } } } Listing 250: Überprüfen der Erreichbarkeit eines Hosts Netzwerke. public class Start { public static void main(String[] args) { if(args != null && args.528 >> Erreichbarkeit überprüfen import java. } // Ergebnis ausgeben System.equals("0")) { try { timeout = Integer. übergebenes Timeout einzulesen String to = args[1]. E-Mail Der hier vorgestellten Konsolenanwendung wird über ihre Argumente der zu überprüfende Host und – optional – das maximale Timeout für die Überprüfung der Erreichbarkeit angegeben: . timeout. try { // Erreichbarkeit prüfen reachable = CheckAvailability.printStackTrace(). } catch (IOException e) { e.out.

die diese Aufgabe erledigen. In Rezept 72 finden Sie ein Beispiel zu diesem Thema. Da damit gleichzeitig die Plattformunabhängigkeit von Java aufgehoben wird. p).start(). Die Rückgabe von netstat kann entweder direkt in eine Textdatei geschrieben und anschließend weiterverwendet oder eingelesen und innerhalb der Anwendung analysiert werden: import java. ist der Einsatz einer derartigen Lösung gründlich zu durchdenken. Listing 251: Abrufen des Verbindungsstatus H in we is . einen Überblick über den aktuellen Status der offenen Verbindungen eines Systems zu erhalten.getRuntime(). ob ein System im Netzwerk angesprochen werden kann.io. /** * Klasse zum Aufruf von netstat unter Windows */ public class Netstat extends Thread { public void run() { // Runtime-Instanz erzeugen Runtime r = Runtime.*. das alle bestehenden Verbindungen samt deren Status auf einem System anzeigen kann. zu erfahren.exe /C netstat -anb").Runtime-Klasse aufgerufen werden. weil PING eine weit verbreitete Möglichkeit ist.exec("cmd. Unter Windows wird dafür das Tool netstat eingesetzt. try { // Process-Instanz erzeugen p = r.lang.>> Netzwerke und E-Mail 529 Abbildung 108: Ein Host ist erreichbar Neben dem Prüfen der Erreichbarkeit eines Servers per ECHO-Request muss oder soll oftmals ein PING durchgeführt werden – allein schon deshalb. E-Mail Das netstat-Tool muss über die java. // Lesen der Ausgabe new OutputReader(this. Process p = null. Je nach Betriebssystem gibt es jedoch verschiedene Konsolenprogramme. 191 Status aller offenen Verbindungen abfragen Leider gibt es keine direkte Möglichkeit. Netzwerke.

bliebe die Java-Anwendung einfach stehen. /** * Hilfsklasse für den Aufruf von netstat unter Windows */ public class OutputReader extends Thread { private InputStream in. werden der aufrufende und der aktuelle Thread beendet und somit auch die Abarbeitung des im aufrufenden Thread gestarteten Prozesses unterbrochen: import java. // Aufrufenden Thread merken Listing 252: Einlesen und Ausgeben der Rückgabe des externen Prozesses . private Thread caller. E-Mail Unter Windows XP/Vista kann netstat nur mit entsprechenden Benutzerrechten ausgeführt werden. Process p) { // InputStream abrufen in = p. um die Ausgabe entgegenzunehmen und weiterzuverarbeiten.*. Der Konstruktor der Klasse OutputReader nimmt als Parameter den aufrufenden Thread und die java. } // Ausnahmen abfangen catch (IOException e) { return. Innerhalb ihrer run()-Methode wird die Ausgabe des netstat-Tools eingelesen und verarbeitet. } catch (InterruptedException e) { return. // Konstruktor OutputReader(Thread caller.getInputStream(). Wenn wie gewöhnlich mit netstat als externem Prozess gearbeitet werden würde. in deren Kontext die netstat-Ausführung stattfindet.Process-Instanz entgegen.) Das Handling von netstat unter Windows ist allerdings nicht befriedigend umgesetzt. Sobald keine Ausgabe mehr erfolgt. Dies liegt weniger an Java als vielmehr an der Implementierung dieses Tools. Aus diesem Grund ist die Klasse als Ableitung von Thread ausgeführt und verwendet einen OutputReader-Thread. } } } Listing 251: Abrufen des Verbindungsstatus (Forts.waitFor().io. A c h t u ng Netzwerke.530 >> Status aller offenen Verbindungen abfragen // Ausführen p.lang.

} } catch (IOException e) { e.start().println(line).exit(0). dann beenden if(null == in) { return.io. } } } Listing 252: Einlesen und Ausgeben der Rückgabe des externen Prozesses (Forts. E-Mail . } } Listing 253: Erzeugen einer neuen Netstat-Instanz und Starten dieser Instanz Netzwerke.printStackTrace().out. try { while(null != (line = br. } // Thread und aufrufenden Thread beenden if(null != caller) { // Aufrufenden Thread unterbrechen caller.caller = caller.interrupt().>> Netzwerke und E-Mail 531 this.Thread-Ableitung ausgeführt ist. return. muss ihre Ausführung über ihre start()-Methode angestoßen werden: public class Start { public static void main(String[] args) { // Erzeugen einer Netstat-Instanz und // starten dieser Instanz new Netstat(). } else { // Ausführung beenden System. } public void run() { // Wenn kein InputStream vorhanden.readLine())) { // Informationen wieder auslesen System. // Informationen zeilenweise einlesen String line = null.) Da die Netstat-Klasse selbst als java. } // BufferedReader zum Auslesen der Informationen BufferedReader br = new BufferedReader( new InputStreamReader(in)).

Netzwerke.util.532 >> E-Mail senden mit JavaMail Bei Ausführung des Programms werden alle offenen Ports des Systems samt der öffnenden Anwendungen angezeigt. Es unterstützt verschiedene Protokolle (SMTP für den Versand sowie POP3 und IMAP für den Empfang und das Verarbeiten von Nachrichten) und kann bei Bedarf um eigene Protokolle erweitert werden. E-Mail JavaMail selbst ist ein komplettes Framework für das Senden und Empfangen von E-Mails.html heruntergeladen werden. Das JavaBeans Activation Framework kann unter http://java. die einige Konfigurationsparameter enthält Erzeugen einer javax. Das Framework benötigt als zusätzliche Komponente das JavaBeans Activation Framework. welcher Mailserver für den Versand verwendet werden soll: .com/products/javamail/ können Sie die derzeit aktuellste Version von JavaMail kostenlos herunterladen.mail.Transport Die Konfigurationsparameter in der Properties-Instanz bestimmen das Verhalten von JavaMail. JavaMail unterstützt verschiedene Nachrichtentypen und kann sehr flexibel konfiguriert werden. Abbildung 109: Ausgabe aller offenen Ports samt deren Status 192 E-Mail senden mit JavaMail Mit Hilfe des JavaMail-Frameworks können Sie E-Mails versenden und abrufen. Unter der Adresse http://java.mail. Mit ihrer Hilfe kann unter anderem festgelegt werden. die die zu versendende Nachricht repräsentiert Versenden der Nachricht per javax.sun. in deren Kontext die weitere Verarbeitung stattfindet Erzeugen einer javax.Session-Instanz.MimeMessage-Instanz. Das Versenden von E-Mails per JavaMail erfolgt in mehreren Schritten: Erzeugen einer java.Properties-Instanz.com/products/javabeans/glasgow/jaf.mail.sun.

internet. // Absender setzen mail. String to. // Session erzeugen Session session = Session. String subject.transport. public class SendMail { /** * E-Mail senden */ public static void send(String from.user Bedeutung Standardprotokoll für die Kommunikation Standard-Mail-Host. wenn der protokollspezifische Username leer oder nicht angegeben ist.Session. Der Platzhalter <protokoll> muss durch das Protokollkürzel ersetzt werden. Der Platzhalter <protokoll> muss durch das Protokollkürzel ersetzt werden.debug Tabelle 47: JavaMail-Umgebungsparameter Nach dem Setzen der benötigten Konfigurationsparameter kann eine E-Mail generiert und versendet werden: import import import import import import javax. ob der JavaMail-Debugging-Modus aktiviert (true) oder deaktiviert ist.Properties. Wird verwendet. Listing 254: Versenden einer E-Mail per JavaMail Netzwerke. // Mail-Repräsentation erzeugen MimeMessage mail = new MimeMessage(session). Gibt die Standard-Antwortadresse an.MessagingException. Standard-Username für den Zugriff auf den Mail-Host. host).mail. java.setFrom(new InternetAddress(from)). wenn der protokollspezifische Host leer oder nicht angegeben ist. javax.host mail.mail.username mail.InternetAddress.protocol mail.mail.host". E-Mail .util. Protokollspezifischer Username. String message.setProperty("mail.internet. Protokollspezifischer Hostname.mail.host mail.mail.Transport.<protokoll>. javax. javax.<protokoll>.getInstance(props).from mail. String host) throws MessagingException { // Properties erzeugen Properties props = new Properties(). javax. mail.MimeMessage. Gibt an. props.>> Netzwerke und E-Mail 533 Parameter mail.smtp. Wird verwendet.

} } Listing 254: Versenden einer E-Mail per JavaMail (Forts. // Text mail..equals("-h")) { host = argVal. E-Mail . // Betreff mail.setText(message). // Nachricht senden Transport.".equals("-f")) { from = argVal. // Zuweisen der Argumente zu den lokalen Variablen if(argKey.534 >> E-Mail senden mit JavaMail // Empfänger setzen mail. new InternetAddress(to)). // Wert String argVal = arg.equals("-t")) { to = argVal. 2). Listing 255: Senden einer Nachricht Netzwerke.substring(2)..".length > 0) { for(String arg : args) { // Schlüssel String argKey = arg. public class Start { public static void main(String[] args) { String from = ".setSubject(subject). String subject = "Test-Email via JavaMail".setRecipient(MimeMessage.". String message = "Diese Email ist über JavaMail gesendet worden.RecipientType. } else if(argKey.substring(0. Zeilenumbrüche zu verwenden.".TO. String host = ".) Das Versenden einer E-Mail über die hier beschriebene statische Methode SendMail. // Parameter überprüfen if(null != args && args...MessagingException.mail. } else if(argKey..send(mail).\r\n\r\n" + "Dabei ist es auch möglich.. String to = ".send() gestaltet sich sehr einfach: import javax.

E-Mail .>> Netzwerke und E-Mail 535 } else if(argKey.send(from.printStackTrace(). } else { message = argVal.sun. Die zu verwendende SMTPTransport-Instanz wird über die Methode getTransport() der bereits zuvor genutzten javax.) Die so generierte Nachricht wird umgehend versendet. Benutzername und Kennwort übergeben werden. subject. } } } Listing 255: Senden einer Nachricht (Forts. Deren connect()-Methode können als Parameter Mailserver. die benötigten Informationen zu Username und Host mitzugeben. to. Abbildung 110: Per JavaMail gesendete Nachricht 193 E-Mail mit Authentifizierung versenden Nicht jeder Mailserver erlaubt den Versand von Nachrichten ohne vorherige Authentifizierung.mail.equals("-s")) { subject = argVal. Dazu wird eine com.SMTPTransport-Instanz verwendet.smtp.Session-Instanz abgerufen: Netzwerke. message. die den Versand von E-Mails per SMTP vornimmt. } catch (MessagingException e) { e. } } } // Nachricht versenden try { SendMail. host). Das JavaMail-Framework gestattet es aber.mail.

setSubject(subject). new InternetAddress(to)).mail. // Mail-Repräsentation erzeugen MimeMessage mail = new MimeMessage(session).send(mail). public class SendMail { /** * E-Mail mit Authentifizierung senden */ public static void sendAuth(String from.MimeMessage.smtp. String subject. // Empfänger setzen mail. javax.mail.getInstance(props). javax. javax.Properties.SMTPTransport. String password) throws MessagingException { // Properties erzeugen Properties props = new Properties(). // Betreff mail.Transport. javax. String message.mail.smtp.setFrom(new InternetAddress(from)).host". host). // SMTPTransport-Instanz referenzieren SMTPTransport smtp = (SMTPTransport) session.RecipientType.internet.setText(message).InternetAddress.MessagingException.setProperty("mail.536 >> E-Mail mit Authentifizierung versenden import import import import import import import com. E-Mail // Absender setzen mail.setRecipient(MimeMessage. String username.TO. javax. } } Listing 256: Versenden einer E-Mail über einen Server mit Authentifizierung . String host.mail. java. Netzwerke. // Nachricht senden Transport. props. String to.internet. // Session erzeugen Session session = Session.connect(host.mail. password).mail.getTransport("smtp"). smtp. username.sun.Session. // Text mail.util.

Listing 257: javax.activation.DataSource-Implementierung */ public class HtmlDataSource implements DataSource { // Darzustellender Text private String text.activation. Es ist jedoch kein großer Aufwand nötig. die zu diesem Zweck verwendet werden kann. um eine eigene DataSource-Implementierung zu erstellen.io. handelt es sich bei diesen Mails streng genommen nicht mehr um textbasierte E-Mails. import java.activation.String getContentType() java. E-Mail .DataSource.text = text.activation.OutputStream getOutputStream() Diese Methoden müssen in der abgeleiteten Klasse implementiert werden.InputStream getInputStream() java.activation. die über ihre Methode getInputStream() den Zugriff auf den zu versendenden HTML-Code erlaubt. } /** * Text abrufen */ public void setText(String text) { this. denn hier wird der Inhalt nicht mehr über die Convenience-Methode setText() zugewiesen.String getName() java. Für eine Klasse HtmlDataSource zum Verarbeiten von HTML-Texten kann dies so aussehen: import javax.io.io. Der Konstruktor der DataHandler-Klasse nimmt dabei unter anderem eine javax. Das Interface javax. /** * Text erfassen */ public String getText() { return text.activation. sondern per javax.DataSource definiert folgende Methoden: java. sondern um E-Mails mit einem Anhang im text/html-Format.lang. Die zu verwendende DataHandler-Instanz wird der MimeMessage-Instanz über deren Methode setDataHandler() zugewiesen.lang.DataSource-Implementierung entgegen.DataSource-Implementierung Netzwerke. Dies muss beim Erstellen der E-Mail berücksichtigt werden. /** * javax. DataSource Leider existiert weder im JavaBeans Activation Framework noch im JavaMail-Framework eine geeignete DataSource-Implementierung zum Versenden von HTML-E-Mails.*.DataHandler-Instanz eingelesen.>> Netzwerke und E-Mail 537 194 HTML-E-Mail versenden Wenn E-Mails im HTML-Format versendet werden sollen.

.ByteArrayInputStream-Instanz erzeugt. wird hier eine neue java. } } Listing 257: javax. Ebenfalls eine Rolle bei der weiteren Verarbeitung des Inhalts der DataSource spielt die Methode getContentType().setText(text).io. Per getText() und setText() kann auf diesen Text zur Laufzeit zugegriffen werden. In diesem Fall handelt es sich um den Inhaltstyp text/html. Da die Methode getInputStream() eine java. } /** * OutputStream.538 >> HTML-E-Mail versenden } /** * Konstruktor */ public HtmlDataSource(String text) { this. Deren Konstruktor nimmt ein Byte-Array entgegen.InputStream-Implementierung zurückgeben muss.activation. die den HTML-Code repräsentiert. E-Mail Beim Instanzieren einer HtmlDataSource-Instanz muss deren Konstruktor der darzustellende HTML-Text als String übergeben werden. in den die Daten geschrieben werden können */ public OutputStream getOutputStream() throws IOException { return new ByteArrayOutputStream(). } /** * Content-Type des Inhalts */ public String getContentType() { return "text/html". das mit Hilfe der Methode getBytes() der String-Instanz erzeugt werden kann. } /** * InputStream zum Einlesen der Daten */ public InputStream getInputStream() throws IOException { return new ByteArrayInputStream(getText(). durch den die E-Mail im Mailprogramm erst als HTML-E-Mail behandelt werden kann.) Netzwerke.getBytes()).io. die den Inhaltstyp der repräsentierten Daten zurückgibt. } /** * Name der DataSource */ public String getName() { return "HtmlDataSource".DataSource-Implementierung (Forts.

DataHandler-Instanz entgegennimmt.Properties.mail.mail. String to. Listing 258: Versand einer HTML-E-Mail Netzwerke. // Absender setzen mail.TO.setDataHandler(dh).mail.RecipientType.mail.util. javax.SMTPTransport. javax. String password) throws MessagingException { // Properties erzeugen Properties props = new Properties(). dass statt der Convenience-Methode setText() nunmehr die Methode setDataHandler() verwendet wird.Transport. java. String host.MessagingException. Deren Konstruktor erhält die zuvor erzeugte javax. // Betreff mail. host).getInstance(props).setRecipient(MimeMessage. String subject. die eine javax.InternetAddress.internet.internet. javax. mail.setProperty("mail.smtp. // Session erzeugen Session session = Session.smtp.MimeMessage. String username.sun. javax.host". // HTML-Nachricht erfassen DataHandler dh = new DataHandler( new HtmlDataSource(message)). String message.>> Netzwerke und E-Mail 539 Versand der E-Mail Der eigentliche Versand der E-Mail unterscheidet sich nicht wesentlich vom oben gezeigten Vorgehen – mit dem Unterschied.Session.DataSourceImplementierung. public class SendMail { /** * E-Mail mit HTML-Text senden */ public static void sendHtml(String from.DataHandler.activation. javax.mail.activation. // Mail-Repräsentation erzeugen MimeMessage mail = new MimeMessage(session). props. E-Mail .setFrom(new InternetAddress(from)). // Empfänger setzen mail.mail. javax.setSubject(subject). die den Zugriff auf den eigentlichen Inhalt erlaubt: import import import import import import import import com.activation. new InternetAddress(to)).

. // Nachricht senden Transport. Hier wird die E-Mail als Text.connect(host. smtp. dass Sie keinen Einfluss auf die Anzeige im E-Mail-Programm des Empfängers haben. werden Sie eine Darstellung im HTML-Format nicht erzwingen können. Wenn dieses nicht entsprechend konfiguriert ist. während Mail-Clients.und HTML-E-Mail versendet.MimeMultipart-Instanz zusammengefasst werden. den HTML-Teil der E-Mail anzeigen. da die E-Mail sonst nicht korrekt dargestellt werden würde. E-Mail Abbildung 111: Per JavaMail als HTML versendete E-Mail 195 E-Mail als multipart/alternative versenden E-Mails im HTML-Format können Darstellungsprobleme bei Mail-Clients verursachen. Mail-Clients. stellen den Textteil der E-Mail dar. Beachten Sie jedoch.als auch HTML-Teil sollten den gleichen Text enthalten. username. damit jedes Mail-Programm die optimierte Version anzeigen kann. Deren Konstruktor nimmt die Bezeichnung eines alternativen Multipart-Typs entgegen – in diesem Fall muss es der Typ alternative sein. Als Lösung für dieses Problem hat sich der Versand derartiger E-Mails im Format multipart/alternative etabliert. password). die aus Sicherheitsgründen auf die Anzeige von HTML verzichten. die eine reine Textansicht bevorzugen. Netzwerke.getTransport("smtp").send(mail). müssen diese über eine javax. die HTML darstellen können und wollen.540 >> E-Mail als multipart/alternative versenden // SMTPTransport-Instanz referenzieren SMTPTransport smtp = (SMTPTransport) session.) Die so versendete E-Mail kann vom E-Mail-Programm im HTML-Format angezeigt werden. Wenn E-Mails aus mehreren Teilen bestehen sollen. } } Listing 258: Versand einer HTML-E-Mail (Forts. T ip p Sowohl Text.mail.

javax.InternetAddress. String subject.mail.smtp. public class SendMail { /** * E-Mail als multipart/alternative senden */ public static void sendMultipartAlternative(String from. String username.MimeBodyPart-Instanz repräsentiert.setFrom(new InternetAddress(from)).getInstance(props). // Multipart-Nachricht erfassen MimeMultipart mp = new MimeMultipart("alternative"). Text zuweisen MimeBodyPart text = new MimeBodyPart().setSubject(subject). new InternetAddress(to)).mail.internet. javax. javax. javax.RecipientType. String password) throws MessagingException { // Properties erzeugen Properties props = new Properties().MessagingException. host). String message. props. javax.mail.mail.MimeMessage.mail. // Einzelne Elemente anfügen: // 1. E-Mail . die der MimeMultipart-Instanz zugewiesen werden muss. String to. javax.DataHandler.host".MimeMultipart.util.activation.mail. Diese wird ihrerseits der Nachricht über deren Methode setContent() zugewiesen: import import import import import import import import import import com.internet. // Betreff mail.TO.MimeBodyPart.Properties. // Absender setzen mail.Session. String host. Listing 259: Versenden einer E-Mail im Format multipart/alternative Netzwerke.sun.Transport. String htmlMessage.mail. // Empfänger setzen mail.setProperty("mail.>> Netzwerke und E-Mail 541 Jeder einzelne Teil wird durch eine javax.setText(message).mail.smtp.internet.internet.SMTPTransport. javax.setRecipient(MimeMessage. java. // Mail-Repräsentation erzeugen MimeMessage mail = new MimeMessage(session). javax. // Session erzeugen Session session = Session. text.mail.

) Eine derart versendete E-Mail wird vom E-Mail-Programm im für den Anzeigemodus am besten geeigneten Format dargestellt: als Plain-Text.getTransport("smtp"). } } Listing 259: Versenden einer E-Mail im Format multipart/alternative (Forts. wenn nur dies unterstützt wird. // 2. // SMTPTransport-Instanz referenzieren SMTPTransport smtp = (SMTPTransport) session. HTML-Teil zuweisen MimeBodyPart html = new MimeBodyPart().setDataHandler(dh). // Multipart-Element der Mail zuweisen mail. html. wenn die Einstellungen dies erlauben.addBodyPart(text).send(mail). // Nachricht senden Transport. DataHandler dh = new DataHandler( new HtmlDataSource(htmlMessage)). Netzwerke.connect(host. oder als HTML. password). username. E-Mail Abbildung 112: Darstellung der E-Mail als Nur-Text .addBodyPart(html).setContent(mp). mp.542 >> E-Mail als multipart/alternative versenden mp. smtp.

mail.oder eine String-Instanz entgegen.mail.internet.util.FileDataSource-Instanz einer MimeBodyPart-Instanz zugewiesen.Properties.activation.MimeMultipart. java.internet.activation.mail.File. String subject.sun. ob Text oder HTML) zugewiesen werden.smtp. String username. javax. E-Mail .mail.File.DataHandler. File file. Deren Konstruktor nimmt eine java. die den zu versendenden Datei-Anhang repräsentiert: import import import import import import import import import import import import com.>> Netzwerke und E-Mail 543 Abbildung 113: Darstellung der E-Mail als HTML 196 E-Mail mit Datei-Anhang versenden Beim Versand von Datei-Anhängen kommt ein javax.MimeMultipart-Element zum Einsatz. javax. javax. java.mail. String host.io.mail. javax.MimeMessage. javax.io. String to.mail.MimeBodyPart.MessagingException. String message. String password) throws MessagingException { // Properties erzeugen Listing 260: Versenden einer E-Mail mit Datei-Anhang Netzwerke.FileDataSource.activation. public class SendMail { /** * E-Mail mit Datei-Anhang senden */ public static void sendAttachment(String from.SMTPTransport. Diesem kann zunächst der eigentliche Nachrichtentext (egal.Transport.InternetAddress. javax.mail.internet. Anschließend wird die Datei per javax. javax.Session.mail. javax. javax.internet.

// SMTPTransport-Instanz referenzieren SMTPTransport smtp = (SMTPTransport) session.getName()).getTransport("smtp").addBodyPart(filePart).setContent(mp). // Empfänger setzen mail. filePart. new InternetAddress(to)).setText(message). username. mp. mp.) .send(mail). E-Mail // 2.setProperty("mail. // Einzelne Elemente anfügen: // 1. // Absender setzen mail.RecipientType. smtp.addBodyPart(text).setDataHandler(dh). text.setRecipient(MimeMessage.getInstance(props). Datei-Referenz einfügen MimeBodyPart filePart = new MimeBodyPart(). } } Listing 260: Versenden einer E-Mail mit Datei-Anhang (Forts. host). Text zuweisen MimeBodyPart text = new MimeBodyPart(). props. // Dateiname setzen filePart.host". DataHandler dh = new DataHandler( new FileDataSource(file)).544 >> E-Mail mit Datei-Anhang versenden Properties props = new Properties().smtp.setSubject(subject). // Nachricht senden Transport. // Betreff mail.connect(host. // Multipart-Element der Mail zuweisen mail. password).setFileName(file. // Multipart-Nachricht erfassen MimeMultipart mp = new MimeMultipart().TO.setFrom(new InternetAddress(from)). Netzwerke. // Mail-Repräsentation erzeugen MimeMessage mail = new MimeMessage(session). // Session erzeugen Session session = Session.

io.mail. wird eine FileNotFoundException geworfen. die einen Speicherort von Nachrichten repräsentiert. Die Implementierung für den Zugriff auf ein POP3-Postfach verwendet dabei den Standardordner INBOX. Ist dies nicht der Fall. E-Mail Abbildung 114: E-Mail mit Datei-Anhang . 197 E-Mails abrufen Das Abrufen von E-Mails über das »nackte« POP3-Protokoll ist beim Einsatz von JavaMail nicht notwendig. die ihrerseits durch javax. Größe. Ein korrektes Speichern des Anhangs wäre so beim Client eventuell nicht mehr möglich.mail. denn das Framework bringt schon alles Notwendige mit. Der Inhalt dieser Store-Instanz kann in Ordnern organisiert sein.mail. Die Vorgehensweise ist dabei nicht am POP3-Protokoll orientiert.>> Netzwerke und E-Mail 545 In diesem Beispiel wird die Übergabe einer java. Empfangsdatum und Multipart-Status ausgegeben. Insbesondere bei der Verarbeitung des Absenders ist Aufmerk- Netzwerke.Store-Instanz abrufen Standardordner abrufen und zur INBOX wechseln Nachrichten per getMessages() abrufen und weiterverarbeiten Aufräumen der genutzten Ressourcen Das Verarbeiten der einzelnen Nachrichten ist in diesem Beispiel relativ einfach gehalten: Zu jeder Nachricht werden die Informationen zu Absender. Die Nachrichten liegen chronologisch geordnet vor.StoreInstanz.Folder-Instanzen repräsentiert werden können. dessen Inhalt über die Methode getMessages() der FolderInstanz abgerufen werden kann.mail. A cht un g Vergessen Sie nicht.File-Instanz an die Methode erwartet. Die referenzierte Datei muss tatsächlich existieren.Session erzeugen javax. Betreff. Die Vorgehensweise beim Abrufen der E-Mails sieht wie folgt aus: javax. den Dateinamen des Anhangs zu setzen. sondern bedient sich einer javax. da sonst ein interner Dateiname zum Versand verwendet wird.

Listing 261: Abrufen und Verarbeiten von E-Mails per JavaMail Netzwerke.getDefaultFolder(). E-Mail . password). Das Abrufen der genannten Informationen findet übrigens sehr ressourcenschonend statt: Statt die komplette Nachricht abzurufen. werden für diese Ausgaben nur die Header-Informationen herangezogen. null). denn nicht immer wird ein Name für einen Absender angegeben – und manchmal (Nachrichten. die neuesten E-Mails werden zuerst ausgegeben.getDefaultInstance(props. d.util. user.InternetAddress. java.getFolder("INBOX").Message-Instanz statt: import import import import javax. was die Geschwindigkeit der Verarbeitung deutlich steigert. Session session = Session.Properties.mail.mail. String password) { Store store=null. javax.getStore("pop3").internet. } // Nachrichten liegen stets im Ordner INBOX folder = folder.Date. // POP3-Store instanzieren und mit Server verbinden store = session. Folder folder=null.connect(server.*. die per CC oder BCC gesendet werden. Innerhalb der Methode processMessage() findet dann die Ausgabe der Informationen zur übergebenen javax. Das eigentliche Abrufen der Inhalte findet nur bei Bedarf statt – und der besteht hier nicht. // Auf den Standardordner zugreifen folder = store. // Standardordner kann nicht gefunden werden if (folder == null) { throw new Exception("No default folder"). String user.mail. die die E-Mails im INBOX-Ordner in umgekehrter chronologischer Reihenfolge durchläuft. store..546 >> E-Mails abrufen samkeit geboten.getProperties(). Im folgenden Beispiel findet das Abrufen der E-Mails innerhalb der Methode readAll() statt. public class ReadMail { /** * Ruft die im angegebenen E-Mail-Konto enthaltenen E-Mails in * umgekehrter Reihenfolge (neueste zuerst) ab * @param server Name oder IP-Adresse des Mailservers * @param user Username für den Zugriff * @param password Password für den Zugriff */ public static void readAll(String server. try { // Session-Instanz erzeugen Properties props = System.h. java. etwa Newsletter) existiert gar überhaupt keine Absenderangabe.util.

} // Ordner öffnen folder.close().getPersonal()) { // Wenn ein Name angegeben ist. } if (store!=null) { store.printStackTrace(). E-Mail . msgNum--) { // Nachricht verarbeiten processMessage(msgs[msgNum]). dann wird dieser // als Absender angenommen from = fromAddress.close(false).printStackTrace().length . } else { Listing 261: Abrufen und Verarbeiten von E-Mails per JavaMail (Forts. for (int msgNum = msgs.getPersonal().) Netzwerke. } } catch (Exception ex) { ex. } } catch (Exception ex2) { ex2.>> Netzwerke und E-Mail 547 // Posteingang kann nicht gefunden werden if (folder == null) { throw new Exception("No POP3 INBOX").getMessages().1. msgNum >= 0.READ_ONLY).getFrom()[0]. String from = null. // Messages abrufen und verarbeiten Message[] msgs = folder. } finally { // Aufräumen try { if (folder!=null) { folder. } } } /** * Gibt die Informationen der übergebenen Message-Instanz aus * @param message Zu verarbeitende Message */ private static void processMessage(Message message) { try { // Absender ermitteln InternetAddress fromAddress = (InternetAddress)message. if(null != fromAddress) { if(null != fromAddress.open(Folder.

// Größe ausgeben System.getContentType().printStackTrace().mail.println("(Multipart-Email)"). Object content = messagePart. System.format("Inhalts-Typ: %s".getSentDate(). ob es sich bei der Nachricht // um eine Multipart-Nachricht handelt if (content instanceof Multipart) { System.println("*************************************"). } } } Listing 261: Abrufen und Verarbeiten von E-Mails per JavaMail (Forts.out.out. also die eigentliche // E-Mail-Adresse verwenden from = fromAddress. // Eigentliche Nachricht abrufen Part messagePart = message.out.format("Datum: %tc".out.format("Groesse: %0.println(String.println(String.out. message. from)).out.format("Absender: %s". die eine per POP3-Protokoll empfangene Nachricht repräsentiert.548 >> E-Mails abrufen // Kein Name angegeben. // Betreff ausgeben String subject = message.getAddress(). System.out. als hier verwendet wurden. subject)).MimeMessage. } } System.) Netzwerke. // Überprüfen. System.getSize())).2d Byte". verfügt über wesentlich mehr Informationen.println( String.println(String. // Datum ausgeben Date date = message. . contentType)).getContent().getSubject(). } catch (Exception ex) { ex.println(String.format("Betreff: %s". System. } // Inhalts-Typ abrufen String contentType = messagePart. E-Mail Die Klasse javax. date)).

io. wie verschiedene Mail-Programme E-Mails erzeugen. E-Mail Gibt alle Header-Felder zurück. Gibt eine javax. Beschreibung Gibt alle Empfänger der E-Mail (Werte aus den TO. Gibt den Wert des »Content-Type«-Header-Felds zurück. Gibt den Wert des »Content-Transfer-Encoding«-HeaderFelds zurück. Gibt den Inhalt der E-Mail zurück.mail.Flags-Instanz zurück. Erlaubt den Zugriff auf den Inhalt als InputStream. Gibt den Wert des »Content-Language«-Header-Felds zurück. Gibt den Wert des »Content-ID«-Header-Felds zurück. die den Status der Nachricht repräsentiert.InputStream getContentStream() String getContentType() String getDescription() String getDisposition() String getEncoding() String getFileName() Flags getFlags() .Enumeration getAllHeaders() Address[] getAllRecipients() Object getContent() String getContentID() String[] getContentLanguage() protected java. mit besonderer Vorsicht zu behandeln. mit denen gearbeitet werden soll. Tabelle 48: Methoden.util. Dies liegt weniger am Protokoll. Netzwerke.>> Netzwerke und E-Mail 549 Abbildung 115: Abrufen von E-Mails per JavaMail Das Abrufen und Parsen von E-Mails ist alles andere als trivial. Gibt den Wert des »Content-Disposition«-Header-Felds zurück. Gibt den Wert des »Content-Description«-Header-Felds zurück. um die Informationen in einer MimeMessage-Instanz abzurufen A c htu n g Methode java. denn die ist häufig alles andere als standardkonform. alle Feldinhalte. als vielmehr an der Art. Gibt den Dateinamen der Nachricht zurück.und CC-Feldern) zurück. Es empfiehlt sich daher.

die alle Elemente der Multipart-Instanz durchläuft und in der Methode handlePart() behandeln lässt. zu dem die Nachricht gesendet worden ist. wird davon ausgegangen. weniger gebräuchliche Methoden zur Verfügung. ob der Inhalt der Mail. Wenn dem so ist. Gibt die Anzahl der Zeilen in der Nachricht zurück. die als Anhang (Attachment) oder inline mitgeführtes binäres Objekt (Inline) gekennzeichnet sind. um die Informationen in einer MimeMessage-Instanz abzurufen (Forts. Gibt den Zeitpunkt zurück. da hierfür lediglich eine Erweiterung der Methode processMessage() notwendig wird. Letzteres sorgt zusätzlich dafür.550 >> Multipart-E-Mails abrufen und verarbeiten Methode Address[] getFrom() String[] getHeader(java. Ist keine ContentDisposition vorhanden.io. vom Typ javax. Gibt die Größe der Nachricht in Byte zurück. Gibt den Wert des »Message-ID«-Header-Felds zurück.util. Innerhalb der Methode processMessage() wird nunmehr überprüft. wird er an die Methode handleMultipart() übergeben.) Netzwerke. RecipientType type) Address[] getReplyTo() Address getSender() java. Gibt die Antwortadressen der Nachricht zurück. Ersteres führt zur direkten Ausgabe des Inhalts. 198 Multipart-E-Mails abrufen und verarbeiten Die Verarbeitung von Multipart-E-Mails lässt sich basierend auf dem in Rezept 197 gezeigten Ansatz recht einfach umsetzen. Tabelle 48: Methoden.mail. Gibt alle Empfänger vom angegebenen Typ zurück. auf die hier aus Platzgründen nicht weiter eingegangen werden soll. .InputStream getInputStream() int getLineCount() String getMessageID() java.String name) java. Erlaubt den Zugriff auf einen dekodierten InputStream. Werfen Sie deshalb auch einen Blick in die Dokumentation von JavaMail. Gleiches gilt für Inhalte. auf den via <MessageInstanz>.Date getSentDate() int getSize() String getSubject() Beschreibung Gibt alle im »From«-Header-Feld definierten Adressen zurück. Gibt den Betreff der Nachricht zurück. die Klasse MimeMessage stellt weitere. zu dem die Nachricht empfangen worden ist.Date getReceivedDate() Address[] getRecipients(Message.lang. Gibt den Absender der Nachricht zurück. Innerhalb der Methode handlePart() wird anhand von Content-Disposition (Inhaltsangabe) und Content-Type (Inhaltstyp) eine Verarbeitung des Inhalts vorgenommen.getContent() zugegriffen werden kann. E-Mail Auch diese Auflistung ist noch nicht komplett. Gibt den Wert des angegebenen Headers zurück. dass der Inhalt im aktuellen Verzeichnis entweder unter seinem eigenen Dateinamen oder einem neu generierten Dateinamen gespeichert wird. Gibt den Zeitpunkt zurück.util. der den Nachrichteninhalt repräsentiert.Multipart ist. dass es sich beim zu behandelnden Inhalt entweder um Plain-Text handelt oder es ein nicht gekennzeichneter Inhalt ist.

ob es sich bei der Nachricht um eine Multipart-Nachricht handelt (content instanceof Multipart) { // Multipart-Nachricht behandeln System. Einzige Besonderheit hier ist die Prüfung darauf.println("(Multipart-Email)"). Dieses Vorgehen wird für alle Elemente der E-Mail wiederholt: import import import import import javax. java..internet. javax..>> Netzwerke und E-Mail 551 Die Speicherung der Daten erfolgt mit Hilfe der Methode saveFile(). E-Mail . // Nachricht verarbeiten processMessage(msgs[msgNum]).mail.. // // // if .io.util. } else { // Normale Nachricht behandeln handlePart(messagePart).. // Eigentliche Nachricht abrufen Part messagePart = message. Überprüfen. public class ReadMail { /** * Ruft die im angegebenen E-Mail-Konto enthaltenen E-Mails in * umgekehrter Reihenfolge (neueste zuerst) ab */ public static void readAll(String server. Object content = messagePart.getContent(). ob in der E-Mail ein Dateiname angegeben worden ist.. java.out.*. } catch (Exception ex) { Listing 262: Verarbeiten von Elementen einer E-Mail Netzwerke.InternetAddress. Falls dem nicht so sein sollte.*. handleMultipart((Multipart) content). Der eigentliche Inhalt der Datei wird anschließend per java.BufferedOutputStream geschrieben.mail...Date..Properties. String user. } /** * Gibt die Informationen der übergebenen Message-Instanz aus */ private static void processMessage(Message message) { try { // .. String password) { // .util. java. } // . wird ein neuer eindeutiger Dateiname generiert.io.. // .

552 >> Multipart-E-Mails abrufen und verarbeiten

ex.printStackTrace(); } } /** * Behandelt eine Multipart-E-Mail */ public static void handleMultipart(Multipart multipart) throws MessagingException, IOException { // Alle Elemente durchlaufen und einzeln behandeln for (int i=0, n=multipart.getCount(); i<n; i++) { // Element behandeln handlePart(multipart.getBodyPart(i)); } } /** * Behandelt einen Teil einer Message */ public static void handlePart(Part part) throws MessagingException, IOException { // Content-Disposition und Content-Type ermitteln String disposition = part.getDisposition(); String contentType = part.getContentType(); // Wenn Content-Disposition null ist, ist das aktuelle // Element nur ein Body-Element if (disposition == null) { // Überprüfen, ob es sich um text/plain handelt // der kann direkt ausgegeben werden if ((contentType.length() >= 10) && (contentType.toLowerCase().substring( 0, 10).equals("text/plain"))) { // Part ausgeben part.writeTo(System.out); System.out.println(); } else { // Fallback-Möglichkeit für unbekannten Body-Typ // Wird beispielsweise für application/octet-stream // aufgerufen System.out.println( String.format("Body-Typ: %s", contentType)); // Part ausgeben part.writeTo(System.out); System.out.println(); // Speichern Listing 262: Verarbeiten von Elementen einer E-Mail (Forts.) Netzwerke, E-Mail

>> Netzwerke und E-Mail

553

saveFile(part.getFileName(), part.getInputStream()); } } else if ( disposition.equalsIgnoreCase(Part.ATTACHMENT) || disposition.equalsIgnoreCase(Part.INLINE)) { // Datei-Anhang oder Inline-Element System.out.println( String.format("%s: %s (%s)", disposition.equalsIgnoreCase(Part.ATTACHMENT) ? "Anhang" : "Inline", part.getFileName(), contentType)); // Speichern... saveFile(part.getFileName(), part.getInputStream()); System.out.println(); } else { // Unbekannter Inhaltstyp System.out.println( String.format("Unbekannt: %s", disposition)); } } /** * Speichert einen Anhang im aktuellen Anwendungsverzeichnis */ public static void saveFile(String filename, InputStream input) throws IOException { // Wenn kein Dateiname vorhanden, dann eine temporäre Datei // anlegen und deren Dateiname verwenden if (filename == null) { filename = File.createTempFile("xxxxxx", ".out").getName(); } // Vorhandene Dateien werden nicht überschrieben File file = new File(filename); for (int i=0; file.exists(); i++) { // Dateiname um eine Zahl erweitern, // damit er eindeutig ist file = new File(filename+i); } // BufferedOutputStream zum Schreiben // in die Datei verwenden BufferedOutputStream bos = new BufferedOutputStream( new FileOutputStream(file)); // BufferedInputStream zum Lesen des Parts BufferedInputStream bis = Listing 262: Verarbeiten von Elementen einer E-Mail (Forts.)

Netzwerke, E-Mail

554 >> URI – Textinhalt abrufen

new BufferedInputStream(input); // Auslesen der Daten und Schreiben in den OutputStream int aByte; while ((aByte = bis.read()) != -1) { bos.write(aByte); } // Ressourcen freigeben bos.flush(); bos.close(); bis.close(); } } Listing 262: Verarbeiten von Elementen einer E-Mail (Forts.)

Netzwerke, E-Mail

Wenn Sie obige Klasse auf eine Multipart-E-Mail, wie sie etwa in Rezpet 196 generiert worden ist, anwenden, werden Sie im Anwendungsverzeichnis zwei oder mehrere Dateien finden: den eigentlichen Datei-Anhang und zusätzlich noch einen eventuell generierten HTML-Bereich, da dieser ebenfalls als Anhang aufgefasst wird. Alle diese Inhalte werden auch auf der Kommandozeile ausgegeben.

199 URI – Textinhalt abrufen
Eine java.net.URL-Instanz repräsentiert einen Verweis auf eine Ressource, wobei nicht zwingend gesagt sein muss, dass es sich dabei um eine Ressource auf einem anderen Rechner oder gar im Internet handeln muss. Typisch sind etwa Verweise auf Dateien (»file://...«), E-MailAdressen (»mailto://...«, die jedoch nicht per java.net.URL-Instanz verarbeitet werden können) oder Verweise auf Inhalte auf anderen Servern (»http://...«, »https://...«, »ftp://...«). Der Abruf von Inhalten geschieht unter Verwendung einer java.io.InputStreamReader-Instanz. Diese nimmt im Konstruktor die InputStream-Instanz entgegen, die die initialisierte URLInstanz durch ihre Methode getStream() zurückgibt. Die zurückgegebenen Zeilen können so lange durchlaufen und verarbeitet werden, bis die InputStreamReader-Instanz keine Inhalte mehr zurückliefert:
import import import import import java.io.BufferedReader; java.io.IOException; java.io.InputStreamReader; java.net.MalformedURLException; java.net.URL;

Listing 263: Inhalt einer externen Ressource einlesen

A c h tun g

Da diverse Mail-Programme und Mailer die Komposition von E-Mails »kreativ« handhaben, kann es beim Abruf von E-Mails oder beim Durchlaufen einzelner BodyElemente zu Abweichungen kommen.

>> Netzwerke und E-Mail

555

public class UrlReader { /** * Liest den Inhalt einer externen Ressource als String ein */ public static String read(String address) { // StringBuffer zum Halten der Daten StringBuffer buff = new StringBuffer(); try { // URL-Instanz, die den Zugriff auf die externe // Ressource erlaubt URL url = new URL(address); // BufferedReader zum Einlesen der Textdaten BufferedReader rdr = new BufferedReader( new InputStreamReader(url.openStream())); // Einlesen der Daten String line = null; while((line = rdr.readLine()) != null) { buff.append(line + "\n"); } // Aufräumen rdr.close(); } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } // Geladene Daten zurückgeben return buff.toString(); } } Listing 263: Inhalt einer externen Ressource einlesen (Forts.)

200 URI – binären Inhalt abrufen
Beim Abruf von binären Inhalten kommt statt eines java.io.BufferedReaders eine java.io.BufferedInputStream-Instanz zum Einsatz. Die Daten werden anschließend mit einer java.io.BufferedOutputStream-Instanz, die einen FileOutputStream kapselt, gespeichert.

A cht un g

Beim Zugriff auf Ressourcen können diverse Ausnahmen auftreten, die abgefangen oder deklariert werden müssen. Typische Ausnahmen sind MalformedURLExceptions (URL-Angabe war nicht gültig) oder IOExceptions (Fehler beim Lesen der Inhalte).

Netzwerke, E-Mail

556 >> URI – binären Inhalt abrufen

Der eigentliche Vorgang des Abrufens findet analog zum Laden des Inhalts einer Textressource statt: Es werden so lange Daten aus dem InputStream in den Puffer geschrieben, bis keine Daten mehr zurückgegeben werden. Der Puffer wird direkt in den Ausgabe-Stream entleert. Sobald der Ausgabe-Stream geschlossen worden ist, ist die abgerufene Datei lokal verfügbar und kann verwendet werden:
import java.io.*; import java.net.MalformedURLException; import java.net.URL; public class UrlReader { /** * Ruft den Inhalt einer externen Ressource ab und speichert ihn */ public static void readAndSaveBinary(String address, String filename) { try { // File-Instanz, die die zu speichernde Datei repräsentiert File file = new File(filename); // URL-Instanz, die die zu ladende Ressource repräsentiert URL url = new URL(address); // OutputStream zum Speichern des Downloads BufferedOutputStream bos = new BufferedOutputStream( new FileOutputStream(file)); // InputStream zum Laden des Downloads BufferedInputStream bin = new BufferedInputStream( url.openStream()); // Puffer von 16.382 Bytes zum Einlesen der Daten byte[] buffer = new byte[16382]; int bytes = 0; // Einlesen und Speichern der Daten while((bytes = bin.read(buffer)) > 0) { bos.write(buffer, 0, bytes); } // Aufräumen bos.close(); bin.close(); } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); Listing 264: Speichern von binärem Content aus einer externen Ressource

Netzwerke, E-Mail

>> Netzwerke und E-Mail

557

} } } Listing 264: Speichern von binärem Content aus einer externen Ressource (Forts.)

201 Senden von Daten an eine Ressource
Mit Hilfe der java.net.URLConnection-Klasse können Daten an eine Ressource gesendet werden – etwa um das Ausfüllen von Formularfeldern zu simulieren. Eine Instanz dieser Klasse kann über die Methode openConnection() einer java.net.URL-Instanz referenziert werden. Durch Übergabe des Werts true an die Methode setDoOutput() der URLConnection-Instanz kann der Modus zum Übertragen von Informationen aktiviert werden. Die zu sendenden Parameter werden als Name-Wert-Paare gesendet. Zum Einsatz kommt dabei eine java.io.PrintWriter-Instanz. Mehrere Name-Wert-Paare werden durch kaufmännisches Und (&) getrennt. Die Antwort des externen Servers kann per java.io.InputStreamReader und einer java.io.BufferedInputStream-Instanz abgerufen und weiterverarbeitet werden:
import import import import import java.util.Properties; java.io.*; java.net.MalformedURLException; java.net.URLConnection; java.net.URL; Netzwerke, E-Mail

public class UrlSender { /** * Übergibt die in der Properties-Instanz data enthaltenen * Informationen an den durch address bezeichneten Server und * liefert dessen Rückgabe zurück */ public static String send(String address, Properties data) { StringBuffer buff = new StringBuffer(); try { // Repräsentation der Ressource URL url = new URL(address); // URLConnection instanzieren URLConnection conn = url.openConnection(); // Output zulassen conn.setDoOutput(true); // PrintWriter erzeugen, mit dem in den Output // geschrieben werden kann PrintWriter outputToServer = new PrintWriter( new OutputStreamWriter(conn.getOutputStream())); Listing 265: Senden von Daten an eine Ressource

558 >> Senden von Daten an eine Ressource

// Hält die Parameter StringBuffer params = new StringBuffer(); // Alle Schlüssel aus der Properties-Instanz auslesen for(Object key : data.keySet().toArray()) { String keyVal = key.toString(); // Wert abrufen String val = data.getProperty(keyVal); // An die zu übergebenden Daten anhängen params.append(String.format("%s=%s&", keyVal, val)); } // Parameter schreiben outputToServer.print(params.toString()); // OutputStream schließen outputToServer.close(); // BufferedReader zum Einlesen der Rückgabe erzeugen BufferedReader in = new BufferedReader( new InputStreamReader(conn.getInputStream())); // Rückgabe einlesen und ausgeben String line = null; while((line = in.readLine()) != null) { buff.append(line + "\n"); } // Aufräumen in.close(); } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } // Ergebnis zurückgeben return buff.toString(); } } Listing 265: Senden von Daten an eine Ressource (Forts.)

Netzwerke, E-Mail

>> Netzwerke und E-Mail

559

Die Daten werden in diesem Beispiel per GET übergeben. Wollen Sie Daten per POST senden, müssen Sie die java.net.URLConnection-Instanz explizit in eine java.net.HttpURLConnection-Instanz casten und deren Methode setRequestMethod() die Art des Zugriffs mitteilen:
((HttpURLConnection) conn).setRequestMethod("POST");

H in we is

Die dabei möglicherweise auftretende ProtocolException muss deklariert oder abgefangen werden.

202 Mini-Webserver
Ein Webserver ist ein sehr gutes Beispiel für die Verbindung von Netzwerkprogrammierung und Thread-Programmierung und eignet sich häufig als Ausgangsbasis für eigene Entwicklungen, da viele Anwendungen den gleichen Kern besitzen: In einer Endlosschleife wird auf eingehende Ereignisse gewartet (beim Webserver auf eine eingehende TCP/IP-Verbindung als Socket) und dann wird jedes Ereignis einem eigenen Thread zugeordnet und von diesem bearbeitet, während das Hauptprogramm wieder in der Endlosschleife auf Ereignisse wartet. Im Folgenden soll ein simpler Webserver gezeigt werden, der auf Anfrage HTML-Seiten bereitstellt. Technisch bedeutet dies, dass der Server auf HTTP-Anfragen des Typs GET reagieren soll. Wenn Sie z.B. www.carpelibrum.de im Browser eintippen, wird er das Kommando GET/ HTTP/1.1 an diejenige IP-Adresse senden1, die mit www.carpelibrum.de verknüpft ist. Ein Webserver muss auf eine GET-Anfrage eine Antwort liefern, die zunächst aus einem Prolog mit Statusangaben und Informationen besteht, gefolgt von einer Leerzeile und den Daten der angeforderten HTML-Seite, z.B.
HTTP/1.0 200 OK Server: Microsoft-PWS/2.0 Date: Wed, 11 May 2005 7:04:55 GMT Content-Type: text/html Accept-Ranges: bytes Last-Modified: Sat, 09 May 1998 09:52:22 GMT Content-Length: 18 Ab hier die Daten Netzwerke, E-Mail

Was der Webserver an Daten zu senden hat, ergibt sich aus der relativen Pfadangabe des Dateinamens in der GET-Anfrage. So besagt z.B. das obige GET / HTTP/1.1, dass das RootVerzeichnis angefordert wird. In der Regel übersetzt der Server dies in eine fest konfigurierte Standarddatei wie /index.html. Wenn der Webserver z.B. als Datenverzeichnis c:\Web verwendet, dann wird er versuchen, die Datei c:\Web\index.html zu lesen und dem anfragenden Browser zu senden. Hierbei muss der Webserver noch mitteilen, um welche Art von Daten es sich handelt (content-type) und wie viele Bytes er senden wird (content-length). Typische Formate für den Datentyp sind »text/html« für HTML-Text und »image/gif« oder »image/jpeg« bei Bilddaten.

1.

Es werden in der Regel noch weitere optionale Zusatzinformationen an den Webserver übertragen, z.B. der Name des Browsers und welche Dateiformate verarbeitet werden können.

560 >> Mini-Webserver

import java.io.*; import java.net.*; import java.util.*; /** * Einfacher Webserver zum Bedienen von GET-Anfragen */ public class MiniWebServer { public static void main(String[] args) { int port = -1; String www_dir = null; if(args.length != 2) { System.out.println("Aufruf mit <www-Verzeichnis> <Port-Nummer>"); System.exit(0); } else { www_dir = args[0]; port = Integer.parseInt(args[1]); } // einen Server-Socket anlegen und auf Anfragen warten int requestID = 0; try { ServerSocket serverSock = new ServerSocket(port); System.out.println("Webserver läuft auf Port " + port); Netzwerke, E-Mail // in einer Endlosschleife auf Anfrage warten while(true) { Socket request = serverSock.accept(); // eine neue Anfrage in eigenem Thread bearbeiten requestID++; RequestThread tmp = new RequestThread(requestID,request,www_dir); tmp.start(); } } catch(IOException e) { System.err.println("Fehler beim Socket-Aufbau!"); e.printStackTrace(); } } } /** * Diese Thread-Klasse bearbeitet die Anfrage */ class RequestThread extends Thread { private Socket mySocket; private int myID; private BufferedReader inStream; private PrintStream outStream; // kann auch Binärdaten enthalten, daher Listing 266: Einfacher Webserver für GET-Anfragen

>> Netzwerke und E-Mail

561

// kein BufferedWriter private String www_dir; // der Konstruktor RequestThread(int id, Socket sock,String dir) { myID = id; mySocket = sock; www_dir = dir; } public void run() { System.out.println("Anfrage " + myID + " wird bearbeitet"); // den Input/Output-Stream eröffnen try { inStream = new BufferedReader(new InputStreamReader(mySocket.getInputStream())); outStream = new PrintStream(mySocket.getOutputStream()); } catch(IOException e) { System.err.println("Anfrage " + myID + " I/O Socket-Stream Exception"); e.printStackTrace(); } // den Header einlesen ArrayList<String> header = readRequestHeader(); printHeader(header); // Kommando und URL extrahieren; der Rest interessiert uns nicht StringTokenizer st = new StringTokenizer((String) header.get(0)); String command = st.nextToken(); String url = st.nextToken(); ArrayList<String> response = new ArrayList<String>(); boolean dataSegment = false; // wir beachten nur das GET-Kommando if(command.equals("GET") == true) { // URI in betriebssystem-spezifischen Dateinamen konvertieren // indem der notwendige Dateitrenner (/ oder \) genommen wird String separator = System.getProperty("file.separator"); StringBuffer buf = new StringBuffer(url.length()); for(int i = 0; i < url.length(); i++) { char c = url.charAt(i); if(c == '/') buf.append(separator); else buf.append(c); Listing 266: Einfacher Webserver für GET-Anfragen (Forts.)

Netzwerke, E-Mail

562 >> Mini-Webserver

} url = buf.toString(); // testen, ob die Datei existiert und gelesen werden kann File file = new File(www_dir + url); if(file.canRead() == true) { // die Antwort zusammenbauen response.add("HTTP/1.0 200 OK"); // den Content-type der gewünschten Datei aufgrund der Dateiendung // setzen if(url.endsWith(".gif") == true) response.add("Content-type: image/gif"); if((url.endsWith(".jpeg") == true) || (url.endsWith(".jpg") == true)) response.add("Content-type: image/jpeg"); if((url.endsWith(".html") == true) || (url.endsWith(".htm") == true)) response.add("Content-type: text/html"); response.add(""); // Leerzeile beendet den Header dataSegment = true; } else { // die gewünschte Datei gibt es nicht response.add("HTTP/1.0 404 Not Found"); response.add("Content-type: text/html"); response.add(""); // Leerzeile beendet die Headersektion response.add("<HTML><HEAD><TITLE> MiniWebServer-Fehlermeldung" + "</TITLE></HEAD>"); response.add("<BODY> Angeforderte Datei nicht vorhanden " + "oder nicht lesbar!</BODY></HTML>"); } } else { // ein nicht unterstütztes Kommando response.add("HTTP/1.0 501 Not implemented"); response.add("Content-type: text/html"); response.add(""); response.add("<HTML><HEAD><TITLE>MiniWebServer-Fehlermeldung " + "</TITLE></HEAD>"); response.add("<BODY> Angeforderte Datei nicht vorhanden " + "oder nicht lesbar!</BODY></HTML>"); } // Die Antwort an den Client schicken. Bei Bedarf auch den Inhalt der // angeforderten Datei als Byte-Stream Listing 266: Einfacher Webserver für GET-Anfragen (Forts.) Netzwerke, E-Mail

>> Netzwerke und E-Mail

563

try { for(String line : response) outStream.println(line); // die gewünschte Datei laden und senden if(dataSegment == true) { byte[] readBuffer = new byte[4096]; int num; try { FileInputStream file = new FileInputStream(www_dir + url); while((num = file.read(readBuffer)) != -1) outStream.write(readBuffer,0,num); file.close(); outStream.flush(); } catch(IOException e) { System.err.println("Anfrage " + myID + ": Datei " + url + " konnte nicht gelesen werden"); } } // Verbindung schließen mySocket.close(); System.out.println("Anfrage " + myID + " abgearbeitet!"); } catch(IOException e) { System.err.println("Anfrage " + myID + ": Header konnte nicht geschrieben werden "); e.printStackTrace(); } } // für Kontrollzwecke: Ausgabe des Headers void printHeader(ArrayList<String> header) { for(String str : header) System.out.println(str); } // Diese Methode liest den Header ein, der vom Client geschickt worden ist ArrayList<String> readRequestHeader() { String line; ArrayList<String> result = new ArrayList<String>(); while(true) { try { line = inStream.readLine(); if(line != null) { Listing 266: Einfacher Webserver für GET-Anfragen (Forts.) Netzwerke, E-Mail

564 >> Mini-Webserver

// der Header endet mit einer Leerzeile if(line.length() <=0) break; else result.add(line); } else break; } catch(IOException e) { System.err.println("Anfrage " + myID + " Exception beim Headerlesen"); e.printStackTrace(); break; } } return result; } } Listing 266: Einfacher Webserver für GET-Anfragen (Forts.)

Netzwerke, E-Mail

Der Webserver benötigt beim Start zwei Parameter: den Namen seines Datenverzeichnisses, wo die zu liefernden Dateien abgelegt sind, sowie den Port, auf dem er lauschen soll (z.B. den Standardport für HTML = 80). Sie können den Server testen, indem Sie in Ihrem Browser eine lokale Datei anfordern, z.B. für die Datei index.html geben Sie als URL ein: http://localhost/ index.html. Zum Beenden des Servers drücken Sie (Strg)+(C).

Abbildung 116: Log-Ausgaben des Mini-Webservers

Schauen Sie gegebenenfalls auch in Rezept 240, falls Sie einen Webserver mit Thread-Pooling realisieren möchten.

XML
203 Sonderzeichen in XML verwenden
Da bestimmte Sonderzeichen Teil der XML-Sprache sind, können sie nicht einfach als Wert eines Elements verwendet werden, sondern müssen durch eine besondere Kodierung (auch als Escape-Sequenz oder Entity bezeichnet) beschrieben werden. Es handelt sich um die folgenden fünf Zeichen:
Zeichen
< > & " '

Escape-Sequence (Entity)
&lt; &gt; &amp; &quot; &apos;

Tabelle 49: Sonderzeichen in XML

Wenn Sie also beispielsweise in ein XML-Element den Text C & A einfügen wollen, müssen Sie schreiben:
<name>C &amp; A</name>

Neben den oben aufgelisteten Entities können auch eigene definiert werden. Zusätzlich dienen Entities dazu, nicht vom Zeichensatz abgedeckte Elemente einzubinden, sofern diese über eine Unicode-Darstellung verfügen:
&#Dezimalcode_im_Unicode-Zeichensatz; &#xHexadezimalcode_im_Unicode-Zeichensatz;

Um beispielsweise einen Zeilenumbruch (Code 13) einzufügen, können Sie folgende Entity verwenden:
<satz>Dies ist ein &#13; umbrochener Satz.</satz>

H in we is

In der Standardeinstellung werden alle führenden und abschließenden WhitespaceZeichen (SPACE, TAB, RETURN) ignoriert, sofern sie nicht durch die Option xml:space geschützt werden.

Eine andere Möglichkeit zur Darstellung von Text mit Sonderzeichen ist der Einsatz von CDATA (siehe Rezept 206).

204 Kommentare
Um ein XML-Dokument oder Teile davon auch später noch verstehen zu können oder ihre Funktion für andere zu dokumentieren, empfiehlt es sich, Kommentare in das Dokument einzufügen. Kommentare dienen aber nicht nur der Dokumentierung. Während der Testphase können Sie die Kommentarzeichen dazu nutzen, einzelne Elemente temporär auszukommentieren.

XML

566 >> Namensräume

Kommentare haben in XML folgende Form:
<!-- Dies ist ein Kommentar -->

205 Namensräume
Da in XML eigene Tags definiert werden können, muss sichergestellt sein, dass gleichnamige Tags auseinander gehalten werden können. Die Lösung dieses Problems ist das auch aus Java bekannte Konzept der Namensräume. Namensräume werden durch das Präfix xmlns, gefolgt vom Kurznamen des Namensraums und einem URL deklariert:
xmlns:cb="http://java.codebooks.de/xml"

Diese Deklaration kann bei der ersten Verwendung eines Namensraums oder auf Ebene des Root-Elements erfolgen. Der URL muss nicht physisch existieren – er dient lediglich der Verdeutlichung der Zugehörigkeit des Namensraums. Allerdings kann am angegebenen Ort ein XML-Schema oder eine DTD hinterlegt sein, wodurch die möglichen Elemente des Namensraums bestimmt werden können:
xmlns:cb="http://java.codebooks.de/xml/validate.dtd"

Nach dieser Deklaration kann der Namensraum verwendet werden:
<cb:book book-number="2294" xmlns:cb="http://java.codebooks.de/xml"> <cb:title>Codebook Java</cb:title> <cb:content> <cb:chapter number="11">Netzwerke</cb:chapter> <cb:chapter number="12">XML</cb:chapter> </cb:content> </cb:book>

Innerhalb eines Dokuments können verschiedene Namensräume definiert werden. Auch die Angabe eines Standard-Namensraums ist möglich. In diesem Fall muss kein Kurzname angegeben werden:
<book book-number="2294" xmlns="http://java.codebooks.de/xml"> ... </book>

XML

A cht un g A c htu n g

Die XML-Definition verbietet es, Kommentare ineinander zu verschachteln.

Beachten Sie, dass alle anderen Elemente, die über keine explizite Namensraumangabe verfügen, implizit dem Standard-Namensraum zugehörig sind. Sollte eine Validierung stattfinden, muss dies natürlich berücksichtigt werden, die betreffenden Elemente müssen im Schema oder der DTD deklariert sein.

>> XML

567

Laut Spezifikation ist das Präfix nur ein Platzhalter für den URL des Namensraums, der dann die eindeutige Zuordnung übernimmt. Zwei gleichnamige Tags, die über unterschiedliche Präfixe den gleichen URL referenzieren, sind demnach also identisch, auch wenn nicht jede Anwendung diese Unterscheidung vornimmt.

206 CDATA-Bereiche
Per Voreinstellung parst ein XML-Parser alle Elemente eines XML-Dokuments. Dies bedeutet, dass diese Elemente wohlgeformt sein müssen. HTML-Tags beispielsweise können so nicht transportiert werden, da sie in der Regel nicht den Anforderungen der Wohlgeformtheit genügen. Um dennoch nicht wohlgeformte Inhalte einbauen zu können, müssen sie als nicht zu interpretierende Zeichenkette (Character Data = CDATA) markiert werden. Ein CDATA-Abschnitt beginnt stets mit der Zeichenkette <![CDATA[ und wird mit ]]> abgeschlossen.
<text><![CDATA[Dieser Text wird nicht interpretiert<br>]]></text>

Ein CDATA-Block kann sowohl Sonderzeichen (Entities) als auch binäre Daten, z.B. Bilder, enthalten. In ihm enthaltene Elemente werden als Text gelesen und nicht interpretiert.

207 XML parsen mit SAX
Das Verarbeiten von XML-Dokumenten per SAX (Simple API for XML, mehr über SAX erfahren Sie beispielsweise unter http://sax.sourceforge.net/) funktioniert ereignisgesteuert. Beim Auftreten bestimmter Ereignisse bindet der SAX-Parser eine org.xml.sax.ContentHandler-Implementierung ein und ruft deren Methoden auf. Das Interface org.xml.sax.ContentHandler definiert folgende zu implementierende Methoden:
void characters( char[] ch, int start, int length ) void endDocument( ) void endElement( java.lang.String uri, java.lang.String localName, java.lang.String qName ) void endPrefixMapping( java.lang.String prefix ) void ignorableWhitespace( char[] ch, int start, int length ) void processingInstruction( java.lang.String target, java.lang.String data ) void setDocumentLocator( Locator locator ) void skippedEntity( java.lang.String name ) void startDocument( ) void startElement( java.lang.String uri, java.lang.String localName, java.lang.String qName, Attributes atts ) void startPrefixMapping( java.lang.String prefix, java.lang.String uri )

H in we is

Diese Methoden werden beim Auftreten von bestimmten Ereignissen eingebunden:
Methode
characters () endDocument() endElement() endPrefixMapping()

Ereignis Textdaten werden verarbeitet. Das Ende des Dokuments ist erreicht. Das Ende eines Tags ist erreicht. Das Ende eines Namensraum-Präfixes ist erreicht.

Tabelle 50: Methoden des Interfaces ContentHandler

XML

Übergibt eine Locator-Instanz. org.*. müssen nur noch die Methoden überschrieben werden.parsers.io. Wenn von dieser Implementierung abgeleitet wird. Beginn eines Tags wird erreicht.DefaultHandler eine Adapter-Implementierung dieses Interfaces. die die eigentliche Anwendungslogik beinhalten sollen.helpers.xml. Ein Namensraum-Präfix wird erreicht.printStackTrace().helpers.newInstance().sax. Eine XML-Entity musste verworfen werden (etwa. um Dokumente per SAX parsen zu können. org. } } /** * Parsen einer XML-Datei * @param Einzulesende Datei */ public void read(File f) { Listing 267: Verarbeiten eines XML-Dokuments per SAX XML . existiert mit der Klasse org. import import import import javax. die alle Methoden als leere Methoden umsetzt. } catch(Exception e) { e. parser = fac. Parsen des Dokuments startet. weil sie nicht deklariert worden ist).*. public SaxReader() { // Parser instanziieren try { SAXParserFactory fac = SAXParserFactory. um alle Knoten eines XMLDokuments samt möglicherweise vorhandenem Inhalt auszugeben. Tabelle 50: Methoden des Interfaces ContentHandler Dieses Interface muss implementiert werden. Dabei wird innerhalb der Methode read() zunächst eine SAXParser-Instanz erzeugt. Um die Arbeit aber ein wenig zu erleichtern.newSAXParser().sax. Anschließend wird der Verarbeitungsprozess über die Methode parse() der SAXParser-Instanz angestoßen.sax.xml. public class SaxReader extends DefaultHandler { private SAXParser parser = null. Im Folgenden wird eine DefaultHandler-Ableitung eingesetzt.568 >> XML parsen mit SAX Methode ignorableWhitespace() processingInstruction() setDocumentLocator() skippedEntity() startDocument() startElement() startPrefixMapping() Ereignis Ignorierbare Leerzeichen werden verarbeitet. Eine Processing-Instruction ist erreicht worden.*. mit deren Hilfe die aktuelle Position innerhalb des XML-Dokuments bestimmt werden kann.xml.xml.*. java.

} catch(SAXException e) { e. } catch(Exception e) { e. this).printStackTrace(). } } /** * Wird aufgerufen.) XML . } } /** * Parsen eines XML-Strings * @param String mit XML-Dokument */ public void read(String document) { try { InputSource input = new InputSource(new StringReader(document)). int start. // Dokument parsen parser. String name. Attributes attributes) throws SAXException { // Ausgeben des Elementnamens System.parse(f. wenn ein Element beginnt * @param uri Namensraum-Präfix * @param name Name des Elements * @param qname Voll qualifizierter Name mit uri und name * @param attributes Attribute * @throws SAXException */ public void startElement(String uri. qname).printStackTrace().printf("Element gefunden: Qualified Name = %s\n".>> XML 569 try { // Dokument parsen parser. } /** * Wird aufgerufen.parse(input. int end) throws SAXException { Listing 267: Verarbeiten eines XML-Dokuments per SAX (Forts.printStackTrace(). wenn ein Text-Element behandelt wird * @param chars Komplettes Dokument * @param start Beginn des Textes * @param end Ende des Textes * @throws SAXException */ public void characters(char[] chars. String qname.console().printStackTrace(). } catch(SAXException e) { e. } catch(Exception e) { e. this).

dann ausgeben if(text..) Wert: %s\n".trim(). end).. --> </content> </book> Listing 268: XML-Beispieldokument XML Abbildung 117: Ausgabe von Elementnamen und -inhalten des Beispieldokuments . // Wenn Text nicht leer ist. start. Das Start-Programm zu diesem Rezept nutzt die Klasse SaxReader zur Analyse des folgenden XML-Dokuments: <?xml version="1.printf(" } } } Listing 267: Verarbeiten eines XML-Dokuments per SAX (Forts.console().length() > 0) { System.570 >> XML parsen mit SAX // Text in String casten und führende bzw..0" encoding="iso-8859-1"?> <book> <title>Java Codebook</title> <authors> <author>Dirk Louis</author> <author>Peter Müller</author> </authors> <publisher>Addison-Wesley</publisher> <content> <chapter number="11">Netzwerk</chapter> <chapter number="12">XML</chapter> <!-. text). folgende // Leerzeichen entfernen String text = new String(chars.

w3c. Das Element ist ein Document-Type-Node. Tabelle 51: Knoten-Typen . Folgende Knoten-Typen können auftreten: Knoten-Typ ATTRIBUTE_NODE CDATA_SECTION_NODE COMMENT_NODE DOCUMENT_FRAGMENT_NODE DOCUMENT_NODE DOCUMENT_TYPE_NODE ELEMENT_NODE ENTITY_NODE ENTITY_REFERENCE_NODE NOTATION_NODE PROCESSING_INSTRUCTION_NODE TEXT_NODE Beschreibung Das Element ist ein Attribut. Hier wird nicht auf das Eintreten bestimmter Ereignisse gewartet. Das Element ist ein CDATA-Bereich. Für den Entwickler bietet dies den unschätzbaren Vorteil. Ob ein Knoten untergeordnete Knoten enthält. XML-Dokumente zu parsen. dass bei DOM das gesamte XML-Dokument im Speicher gehalten wird.Element-Implementierungen repräsentiert. Ein wesentlicher Unterschied zwischen SAX und DOM ist somit.dom. ist – wie bereits mehrfach erwähnt – ein anderer Denkansatz notwendig.und org.w3c.dom.dom. Das Element ist ein Dokumentfragment. besteht in der Verwendung des Document Object Model (DOM). lässt sich mit der Methode hasChildNodes() der Node-Implementierung feststellen. dass sich die verschiedenen Elementtypen. als dies beim Einsatz von SAX der Fall war. wobei jeder Knoten über einen oder mehrere untergeordnete Knoten verfügen kann. Das Element ist eine definierte Entity. Das Element ist reiner Text. Bei der Arbeit mit dem DOM befindet man sich meist auf Knotenelementen. Die Methode getChildNodes() des aktuellen Knotens gibt eine org. XML Das Element ist ein Dokument. Das Element ist ein Kommentar-Element.w3c. Um ein XML-Dokument per DOM zu laden und zu parsen. sondern man traversiert über die Baumstruktur eines XML-Dokuments vom Root-Element hin zu den untergeordneten Elementen. die sich stark an deren Struktur orientiert. die im DOM definiert sind. einer systemunabhängigen Definition des Zugriffs auf XML-Dokumente. Sogar das zugrunde liegende Dokument wird als Node repräsentiert. Der Textinhalt eines Knotens kann per getNodeValue() bestimmt werden. Das Element ist eine Processing-Instruction.>> XML 571 208 XML parsen mit DOM Ein anderer Weg.Node. Das Element ist ein Knoten und kann untergeordnete Inhalte besitzen. Über die Methode getNodeType() kann der Typ des aktuellen Knotens bestimmt werden. weitestgehend identisch handhaben lassen – das Hinzufügen oder Auslesen von Informationen geschieht stets über das gleiche API. Diese werden durch org. Das Element verweist auf eine definierte Entity. Grundprinzip der Arbeit mit dem DOM ist die Orientierung an der Dokumentenstruktur. Das Element ist ein Notation-Element.NodeList-Implementierung zurück. Beim DOM wird das Dokument von außen nach innen durchlaufen. die alle untergeordneten Knoten enthält.

*. } } Listing 269: Verarbeiten eines XML-Dokuments per DOM XML . Da sie sich wie jedes andere XML-Element verhält und das Interface org.parse(f).io.newInstance().DocumentBuilder-Instanz erzeugen XML-Datei laden org. In dieser Methode kann anhand des Knoten-Typs und der Anzahl der untergeordneten Elemente bestimmt werden.*. kann sie analog zu allen untergeordneten Elementen in einer rekursiv arbeitenden Methode verarbeitet werden. zu verarbeiten.w3c. javax.newDocumentBuilder().xml. public class DOMReader { private DocumentBuilder parser = null. ob eine weitere Rekursion erfolgen soll oder ob ein eventuell vorhandener Textinhalt ausgegeben werden kann: import import import import org. mehr über SAX erfahren Sie beispielsweise unter http:// sax.parsers.xml. } catch(Exception e) { e.572 >> XML parsen mit DOM Um ein XML-Dokument analog zum Beispiel aus Rezept Das Verarbeiten von XML-Dokumenten per SAX (Simple API for XML.sourceforge. public DOMReader() throws ParserConfigurationException { DocumentBuilderFactory fac = DocumentBuilderFactory.Node implementiert.parsers.*.*. parser = fac. } /** * Parsen einer XML-Datei * @param Einzulesende Datei */ public void read(File f) { try { // Dokument einlesen Document doc = parser.dom. java.dom.w3c.printStackTrace().sax. müssen vor der eigentlichen Analyse und Verarbeitung der Knoten noch folgende Schritte durchgeführt werden: javax. } catch(SAXException e) { e.printStackTrace().dom.w3c. // Dokument verarbeiten analyze(doc). org. Beim Auftreten bestimmter Ereignisse bindet der SAX-Parser eine org.ContentHandler-Implementierung ein und ruft deren Methoden auf.sax.Document-Instanz abrufen Die so geladene Document-Instanz kann nun verarbeitet werden.xml.xml.net/) funktioniert ereignisgesteuert.

Listing 269: Verarbeiten eines XML-Dokuments per DOM (Forts. // Wenn Textinhalt vorhanden.parse(input).printStackTrace(). } catch(SAXException e) { e.) XML . } } else { // Name des Knotens ausgeben System. // Überprüfen.printStackTrace().TEXT_NODE) { // Wert lokal zwischenspeichern String value = node.getNodeValue().getChildNodes().printf(" Wert: %s\n". // Dokument einlesen Document doc = parser. value).getLength().>> XML 573 /** * Parsen eines XML-Strings * @param String mit XML-Dokument */ public void read(String str) { try { // aus dem String ein InputSource-Objekt erstellen InputSource input = new InputSource(new StringReader(str)). ob untergeordnete Knoten existieren if(node. // Dokument verarbeiten analyze(doc).console().console().getNodeType() == Node.trim().hasChildNodes()) { // Alle untergeordneten Knoten durchlaufen int num = node.printf("Gefundendes Element: %s\n". Inhalt ausgeben if(node != null && node.length() > 0) { System. } } /** * Analysiert den übergebenen Knoten * @param node Zu analysierender Knoten */ private void analyze(Node node) { // Wenn es sich um einen Text-Knoten handelt.getNodeName()). dann ausgeben if(value. node. } catch(Exception e) { e.

574 >> XML parsen mit DOM for(int i=0.getChildNodes().. --> </content> </book> Listing 270: XML-Beispieldokument XML Abbildung 118: Verarbeiten eines XML-Dokuments per DOM . } } } } } Listing 269: Verarbeiten eines XML-Dokuments per DOM (Forts..0" encoding="iso-8859-1"?> <book> <title>Java Codebook</title> <authors> <author>Dirk Louis</author> <author>Peter Müller</author> </authors> <publisher>Addison-Wesley</publisher> <content> <chapter number="11">Netzwerk</chapter> <chapter number="12">XML</chapter> <!-. i++) { // Aktuellen Knoten analysieren analyze(node. i < num..item(i)).) Das Start-Programm zu diesem Rezept nutzt die Klasse DOMReader zur Analyse des folgenden XML-Dokuments: <?xml version="1.

Dafür ist DOM wesentlich flexibler – und spätestens wenn es darum geht. <?xml version="1. Einen guten Einstieg in dieses Thema finden Sie unter http://www. 209 XML-Dokumente validieren XML-Dokumente sollten nach Möglichkeit validiert werden. würde jedoch den Rahmen dieses Buchs sprengen. besonders bei großen Dokumenten. die enthaltenen Daten oder die Struktur zu manipulieren.org/2001/XMLSchema"> <xs:element name="book"> <xs:complexType> <xs:sequence> <!-.0" encoding="iso-8859-1"?> <book> <title>Java Codebook</title> <authors> <author>Dirk Louis</author> <author>Peter Müller</author> </authors> <publisher>Addison-Wesley</publisher> <content> <chapter number="11">Netzwerk</chapter> <chapter number="12">XML</chapter> <!-. ist mittlerweile das XML Schema der übliche Mechanismus. publisher. --> </content> </book> Listing 271: XML-Beispieldokument Das Schema definiert als Root-Element ein book-Element.xsd" xmlns:xs="http://www. muss SAX ohnehin passen.0"?> <xs:schema xmlns="http://tempuri. ob sie einer definierten Struktur entsprechen.h.org/XMLFile1. Auf die genaue Syntax von DTDs oder XML Schemata an dieser Stelle einzugehen. Den authors. Das weiter unten abgedruckte Schema validiert die Struktur des folgenden XML-Dokuments <?xml version="1.bzw.>> XML 575 Neben der unterschiedlichen Herangehensweise an die Analyse und Verarbeitung eines Dokuments unterscheiden sich die Ansätze SAX und DOM insbesondere in einem Punkt: der Verarbeitungsgeschwindigkeit. authors-. chapter-Elemente untergeordnet sein. d. das auf das XML-Beispieldokument angewendet werden soll H in we is XML ...w3. und benötigt viel weniger Speicher.w3schools. man testet.Titel --> <xs:element name="title" Listing 272: buchSchema.asp. SAX ist deutlich schneller als DOM.xsd – Schema..und content-Elementen können author. Während in der Frühzeit von XML vorwiegend ein sogenanntes DTD (Document Type Definition) zum Einsatz kam. um die Syntax eines XML-Dokuments festzulegen.com/schema/default. das title-.und content-Elemente enthalten darf.

576 >> XML-Dokumente validieren type="xs:string" minOccurs="0" /> <!-.xsd – Schema.Ein Autor --> <xs:element name="author" nillable="true" minOccurs="0" maxOccurs="unbounded"> <xs:complexType> <xs:simpleContent> <xs:extension base="xs:string"> </xs:extension> </xs:simpleContent> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> </xs:element> <!-.Einzelne Kapitel --> <xs:element name="chapter" nillable="true" minOccurs="0" maxOccurs="unbounded"> <xs:complexType> <xs:simpleContent> <xs:extension base="xs:string"> <xs:attribute name="number" form="unqualified" type="xs:string" /> </xs:extension> </xs:simpleContent> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> </xs:element> </xs:schema> Listing 272: buchSchema.Inhalt des Buchs --> <xs:element name="content" minOccurs="0" maxOccurs="unbounded"> <xs:complexType> <xs:sequence> <!-.Autoren --> <xs:element name="authors" minOccurs="0" maxOccurs="unbounded"> <xs:complexType> <xs:sequence> <!-.Verlag --> <xs:element name="publisher" type="xs:string" minOccurs="0" /> <!-.) XML . das auf das XML-Beispieldokument angewendet werden soll (Forts.

validation.io. } catch (SAXException ex) { System. benötigt man eine Instanz der Klasse javax. } XML /** * Validiert die übergebene XML-Datei * * @param XML-String Listing 273: Validierung eines Schemas . import import import import import org.validate(input).stream.>> XML 577 Um nun eine XML-Datei gegen ein Schema zu validieren. javax. sonst false */ public boolean validate(String str) throws IOException { boolean result = true. javax. result = false.validation. java. public SchemaValidator(File schemaDatei) throws SAXException { SchemaFactory schemaFac = SchemaFactory.W3C_XML_SCHEMA_NS_URI).*.*.err. die man jedoch nicht direkt erzeugen kann. falls valid.transform.getMessage()).newSchema(schemaDatei).newValidator(). Schema schema = schemaFac.println("Nicht schema-konform: " + ex.xml.sax.xml.xml.xml.*.*. } /** * Validiert den übergebenen XML-String * * @param XML-String * @return true. public class SchemaValidator { private Validator validator = null. validator. javax.newInstance(XMLConstants. validator = schema.*. } return result.xml. try { StreamSource input = new StreamSource(str).Validator. sondern mit Hilfe der Klassen SchemaFactory und Schema besorgen muss.

// Document-Instanz erzeugen doc = docBuilder.parsers.Document. } } Listing 273: Validierung eines Schemas (Forts. Jedes weitere Element wird ebenfalls über createElement() erzeugt und dann dem jeweils übergeordneten Knoten über dessen appendChild()-Methode zugewiesen. Anschließend wird deren Root-Element über die Methode createElement() der Document-Instanz erzeugt.dom. Textinhalte werden über die Methode setTextContent() eines Knotens erfasst.DocumentBuilderFactory.) 210 XML-Strukturen mit Programm erzeugen Das programmgestützte Erzeugen von XML-Strukturen geschieht von außen nach innen: Zunächst wird über eine javax.printStackTrace().xml. } Listing 274: Erzeugen eines XML-Dokuments .w3c. } catch (SAXException ex) { System. try { // DocumentBuilder instanzieren DocumentBuilder docBuilder = DocumentBuilderFactory.newDocumentBuilder(). } return result. public class DocumentCreator { public static Document createProgrammatically() { Document doc = null. Attribute können dem Knoten über dessen Methode setAttribute() zugewiesen werden.out.println("Nicht schema-konform: " + ex.parsers.parsers. } catch (ParserConfigurationException e) { e. falls valid. try { StreamSource input = new StreamSource(f).newDocument().w3c.getMessage()).Element. import import import import import XML org.ParserConfigurationException. org.xml.validate(input). javax.parsers. javax.DocumentBuilder-Instanz eine neue DocumentInstanz erzeugt. validator. result = false.xml. javax.dom.newInstance().578 >> XML-Strukturen mit Programm erzeugen * @return true.DocumentBuilder.xml. sonst false */ public boolean validate(File f) throws IOException { boolean result = true.

) XML .setTextContent("Netzwerk").createElement("chapter").setTextContent("Addison-Wesley"). content.setTextContent("XML"). // Autoren-Element erzeugen und anfügen Element authors = doc.createElement("content"). authors.setTextContent("Peter Müller").appendChild(author). author. // Inhalt anfügen title. content. // Einzelne Kapitel erzeugen und anfügen Element chapter = doc.setAttribute("number".createElement("title").setTextContent("Dirk Louis"). // Titel-Knoten anfügen book. chapter.createElement("publisher"). // Kapitel-Element Element content = doc. chapter. authors.appendChild(book). book.>> XML 579 if(null != doc) { // Root-Element erzeugen Element book = doc. // Titel-Element erzeugen Element title = doc. "12").appendChild(content). // Einzelne Autor-Elemente erzeugen und anfügen Element author = doc. chapter.createElement("authors").appendChild(publisher).appendChild(chapter).appendChild(title). author = doc.createElement("author"). } Listing 274: Erzeugen eines XML-Dokuments (Forts. chapter. publisher.createElement("chapter").appendChild(authors). chapter = doc.createElement("book"). // Verlags-Info Element publisher = doc.setAttribute("number". author. // Element anfügen doc.appendChild(chapter).createElement("author"). "11").appendChild(author). book. book.setTextContent("Java Codebook").

Diese OutputStream-Instanz kann beispielsweise System. welche eine Instanz von javax.DOMSource.out .Document.transform. java. import import import import import org.out */ public static void writeToSystemOut(Document content) { Listing 275: Formatierte Ausgabe eines XML-Dokuments nach System.StreamResult. wodurch die Ausgabe auf Konsole erfolgt.OutputStreamWriter. typische Schritte in kleinen Methoden zu kapseln.xml.transform.stream. wie man vielleicht erwarten würde. die in einen java. Diese transformiert die Document-Instanz in ihrer transform()-Methode in eine javax. Man benötigt hierfür die Klasse TransformerFactory.transform.*.stream. public class DocWriter { /** * Schreibt die übergebene Document-Instanz nach System.StreamResult-Instanz. io.xml.xml.dom.) Das erzeugte XML-Dokument sieht wie folgt aus: <?xml version="1.Transformer erzeugen kann. javax.580 >> XML-Dokument formatiert ausgeben // Document-Instanz zurückgeben return doc.0" encoding="UTF-8" standalone="no"?> <book> <title>Java Codebook</title> <authors> <author>Dirk Louis</author> <author>Peter Müller</author> </authors> <publisher>Addison-Wesley</publisher> <content> <chapter number="11">Netzwerk</chapter> <chapter number="12">XML</chapter> </content> </book> 211 XML-Dokument formatiert ausgeben Die Ausgabe eines XML-Dokuments in gut lesbarer – also eingerückter Schreibweise – ist gar nicht so einfach. javax. } } Listing 274: Erzeugen eines XML-Dokuments (Forts.dom.io.xml.out sein. javax.transform.w3c. XML T ip p Um den Tippaufwand für die Erzeugung von XML-Dokumenten zu minimieren.OutputStream schreiben kann.xml. empfiehlt es sich.transform.

METHOD. new StreamResult( new OutputStreamWriter(System. "text/xml"). // Parameter setzen: Einrücken t. // Content-Type t.) Wird das in Rezept 210 erzeugte XML-Dokument auf diese Art nach System. try { // Transformer-Instanz abrufen t = tf.printStackTrace(). } } } Listing 275: Formatierte Ausgabe eines XML-Dokuments nach System.newTransformer(). new Integer(3)).newInstance().setOutputProperty(OutputKeys.transform(new DOMSource(content). // Transformation durchführen und Ergebnis in einen Stream speichern t. "yes"). // Ausgabe-Typ: xml t.MEDIA_TYPE. // Einrückungstiefe definieren tf.>> XML 581 // TransformerFactory instanzieren TransformerFactory tf = TransformerFactory.printStackTrace(). ergibt sich die Ausgabe aus Abbildung 119.setOutputProperty( OutputKeys. } catch (TransformerConfigurationException e) { e. XML .INDENT.setAttribute("indent-number". } catch (TransformerException e) { e.setOutputProperty(OutputKeys.out))).out geschrieben. "xml").out (Forts. Transformer t = null.

io.xml. try { // Transformer-Instanz abrufen t = tf. new Integer(3)). wie geschrieben wird: Beim Schreiben nach System. javax.newTransformer(). Transformer t = null.xml.DOMSource.io. javax.transform.newInstance().dom.StreamResult.dom. javax. // Einrückungstiefe definieren tf.stream. java.PrintWriter-Instanz zum Einsatz.out kommt eine java.io.*. // Parameter setzen: Einrücken Listing 276: Speichern einer Document-Instanz in eine Datei XML .setAttribute("indent-number".io.xml.w3c. String fileName) { // TransformerFactory instanzieren TransformerFactory tf = TransformerFactory.transform.FileWriter. java. Der einzige Unterschied zwischen den beiden Aufgaben ist die Art.IOException.Document.FileWriter-Instanz verwendet wird: import import import import import import org.transform.582 >> XML-Dokument formatiert als Datei speichern Abbildung 119: Ausgabe eines XML-Dokuments auf die Konsole 212 XML-Dokument formatiert als Datei speichern Analog zur Ausgabe einer Document-Instanz auf die Konsole können Sie auch beim Speichern in eine Datei vorgehen. während beim Speichern in eine Datei eine java. public class DocWriter { /** * Schreibt die übergebene Document-Instanz in die angegebene Datei */ public static void writeToFile(Document content.

printStackTrace(). // Ausgabe-Typ: xml t. "xml"). beim Einsatz von Umlauten die korrekte Kodierung anzugeben.>> XML 583 t. A cht un g . werden auch die enthaltenen Umlaute ordnungsgemäß visualisiert. so wie hier durch Aufruf von setOutputProperty(OutputKeys. schlimmstenfalls ist die XML-Datei nicht mehr lesbar.printStackTrace().setOutputProperty(OutputKeys. Tun Sie dies nicht. } catch (TransformerException e) { e.setOutputProperty(OutputKeys. Ist die korrekte Kodierung gesetzt.MEDIA_TYPE.INDENT. XML Die so gespeicherte Datei kann anschließend weiterverarbeitet werden.ENCODING.) Vergessen Sie nicht.setOutputProperty(OutputKeys. // Encoding setzen t. } catch (IOException e) { e. "text/xml"). } catch (TransformerConfigurationException e) { e. // Content-Type t. } } } Listing 276: Speichern einer Document-Instanz in eine Datei (Forts.setOutputProperty(OutputKeys. "yes"). "iso-8859-1"). "iso-8859-1"). gehen bestenfalls Umlaute verloren oder werden nicht korrekt dargestellt.transform(new DOMSource(content). new StreamResult(fw)).printStackTrace().ENCODING.METHOD. demonstriert. // FileWriter erzeugen FileWriter fw = new FileWriter(fileName). // Transformation durchführen und Ergebnis in einen Stream speichern t.

w3schools.asp. HTML) umgewandelt werden können.asp. wie das XML-Dokument in ein Zielformat (HTML. XML. mit dessen Hilfe XML-Dokumente in andere Zielformate (z. Dieses definiert. Das Kürzel XSLT steht für eXtensible Markup Language for Transformations und bezeichnet einen XMLDialekt. XSLT setzt sehr stark auf den Einsatz von XPath.apache. Eine Einführung in XSLT finden Sie unter der Adresse http://www.B. Diese Transformationen benötigen neben dem XML-Parser auch einen XSLT-Interpreter. Betrachten Sie das folgende XML-Dokument: <?xml version="1.org/ xalan-j/ heruntergeladen werden kann. Wichtigstes Element einer XSLT-Transformation ist neben dem XML-Dokument ein XSLStylesheet. Mehr zu XPath erfahren Sie unter http://www.0" encoding="iso-8859-1"?> <book> <title>Java Codebook</title> <authors> <author>Dirk Louis</author> Listing 277: XML-Beispieldokument XML . Der De-facto-Standard dafür ist Apaches Xalan-J. das sich nur mit der Verwendung von XSLT und XPath befasst.com/xsl/default.584 >> XML mit XSLT transformieren Abbildung 120: Diese Datei ist aus einer Document-Instanz erzeugt worden. Im J2EE Codebook finden Sie ein eigenes Kapitel. 213 XML mit XSLT transformieren Ein sehr häufiger und immer wichtiger werdender Teil der Arbeit mit XML ist XSLT.com/xpath/default. andere textbasierte Formate) transformiert werden soll. die der Lokalisierung von Knoten in einem XML-Dokument dient.w3schools. der unter der Adresse http://xml. einer Technologie.

</div> <div> <!-.Alle Autoren durchlaufen --> <xsl:for-each select=".Einzelnen Autor ausgeben --> <li><xsl:value-of select=".Titel nochmals ausgeben --> <h3><xsl:value-of select=". --> </content> </book> Listing 277: XML-Beispieldokument (Forts." /></li> </xsl:for-each> </ul> </div> <div> <!-.Titel ausgeben --> <title><xsl:value-of select=".xsd – XSL-Stylesheet zur Transformation des XML-Beispieldokuments nach HTML XML .org/1999/XSL/Transform" version="1.0" encoding="iso-8859-1"?> <xsl:stylesheet xmlns:xsl="http://www. <?xml version="1..w3.Verlag ausgeben --> <strong>Verlag</strong><br /> <xsl:value-of select="./title" /></title> </head> <body> <!-./title" /></h3> <div> <!-.) Dieses XML-Dokument soll mit Hilfe des folgenden XSL-Stylesheets nach HTML transformiert werden.Autoren ausgeben --> <strong>Autoren</strong> <ul> <!-.Start-Element ist "Book" --> <xsl:template match="book"> <html> <head> <!-.Inhalte ausgeben --> Listing 278: buchStil.../publisher" /><br />&#160./authors/author"> <!-.0"> <xsl:output method="html" indent="yes" /> <!-.>> XML 585 <author>Peter Müller</author> </authors> <publisher>Addison-Wesley</publisher> <content> <chapter number="11">Netzwerk</chapter> <chapter number="12">XML</chapter> <!-.

io. Anschließend können die Quelldateien der Transformation per javax.) Die Transformation von Dokumenten wird innerhalb der Transformation API for XML (TrAX) beschrieben./content/chapter"> <!-.StreamSource-Instanz referenziert werden.stream." /></li> </xsl:for-each> </ul> </div> </body> </html> </xsl:template> </xsl:stylesheet> Listing 278: buchStil. Eine Transformation benötigt fünf Elemente.xml.newInstance() eine javax. .xsd – XSL-Stylesheet zur Transformation des XML-Beispieldokuments nach HTML (Forts. Die konkrete Implementierung des Interfaces Result repräsentiert das Zieldokument.Kapitel-Nummer und Bezeichnung ausgeben --> <li>#<xsl:value-of select="@number" />: <xsl:value-of select=". Nach dem Referenzieren der beiden Quelldateien für die Transformation wird deren Ziel angegeben. transform.FileOutputStream-Instanz verwendet. um den generierten Output speichern zu können.586 >> XML mit XSLT transformieren <strong>Inhalte</strong><br /> <ul> <!-. um erfolgreich durchgeführt zu werden: Element TransformerFactory Transformer Source Result Beschreibung Diese Factory durchsucht den Klassenpfad nach einem geeigneten Prozessor für die Transformation.transform zur Verfügung. Dieses API steht dem Entwickler innerhalb des Namensraums javax. XML Stylesheet Tabelle 52: Elemente einer erfolgreichen Transformation Der Ablauf einer XSL-Transformation sieht so aus: Zunächst wird über TransformerFactory. In diesem Fall wird eine java.TransformerFactoryInstanz erzeugt.xml. Das Stylesheet wird ebenso wie das Quelldokument durch eine Source-Implementierung repräsentiert. Diese konkrete Implementierung wird von der TransformerFactory erzeugt und steuert die eigentliche Umwandlung.Alle Kapitel durchlaufen --> <xsl:for-each select=".xml. Die konkrete Implementierung des Interfaces Source repräsentiert das Quelldokument.

xml. javax.>> XML 587 Die Transformer-Implementierung.*.stream. String xslFile. nimmt als Parameter die StreamSource-Instanz des XSLT-Stylesheets entgegen und wird von der Methode newTransformer() der TransformerFactory-Instanz erzeugt. java.printStackTrace().transform.StreamSource. } catch (FileNotFoundException e) { e.newTransformer(xsl).io.xml. } catch (TransformerException e) { e.stream. // Transformation durchführen transformer. // Ausgabeziel definieren Result output = new StreamResult( new FileOutputStream(resultFile)). // Transformer erzeugen Transformer transformer = fact. import import import import javax. die als Letztes instanziert werden muss. String resultFile) { try { // Transformer-Factory erzeugen TransformerFactory fact = TransformerFactory.transform(xml.printStackTrace(). javax.newInstance(). Diese verwendet dazu den im Classpath hinterlegten XSLT-Prozessor.transform.StreamResult. // Stylesheet referenzieren Source xsl = new StreamSource( new FileInputStream(xslFile)).xml. // Quelldokument referenzieren Source xml = new StreamSource( new FileInputStream(xmlFile)).*. output). public class XslTransform { /** * Methode zur Durchführung einer XSL-Transformation * * @param Name der XML-Datei * @param Name des XSL-Datei * @param Name der Ausgabedatei */ public static void transform(String xmlFile. Listing 279: Transformation eines XML-Dokuments mit Hilfe eines XSL-Stylesheets XML .transform.und Ausgabestream führt die Transformation durch. Ein Aufruf der Methode transform() der Transformer-Instanz unter Angabe von Quell.

588 >> XML mit XSLT transformieren } } } Listing 279: Transformation eines XML-Dokuments mit Hilfe eines XSL-Stylesheets (Forts. kann sie in jedem Browser angezeigt werden.) Da die generierte Datei aus reinem HTML besteht. Abbildung 121: Generierte HTML-Datei im Browser XML .

Internationalisierung
214 Lokale einstellen
Javas Unterstützung für die Internationalisierung1 von in Java geschriebener Software ruht auf zwei wichtigen Säulen: Unicode als universeller Zeichensatz2 und das Konzept der Lokale zur Anpassung an landes- und kulturspezifische Eigenheiten. Unter einer Lokale (Gebietsschema) versteht man eine politische, geographische oder kulturelle Region, mit eigener Sprache und eigenständigen Regeln für die Formatierung von Datumsangaben, Zahlen etc. In Java-Programmen werden diese Lokalen durch Instanzen der Klasse java.util.Locale repräsentiert. Sprach- und länderspezifische Aufgaben (wie z.B. die Formatierung von Zahlen, Datumsangaben, Stringvergleiche etc., siehe nachfolgende Rezepte) können Sie in Java grundsätzlich auf vier verschiedene Weisen erledigen: Sie scheren sich nicht um Lokale-spezifische Eigenheiten. Wenn Sie beispielsweise Double-Werte mittels toString() in Strings umwandeln, werden die Nachkommastellen immer durch einen Punkt dargestellt (wie in Großbritannien/USA üblich). Sie erledigen die Aufgaben gemäß der Lokale, die auf dem aktuellen System eingestellt ist. So erreichen Sie, dass sich Ihre Anwendung automatisch an die vom Benutzer eingestellten landes- und kulturspezifischen Eigenheiten anpasst (beispielsweise Nachkommastellen in Gleitkommazahlen auf einem US-Rechner mit Punkt und auf einem für deutsche Benutzer konfigurierten Rechner mit Komma abgetrennt). Hierzu müssen Sie sich der jeweiligen Lokale-sensitiven Klasse/Methode bedienen, die Java zur Lokale-typischen Erledigung der Aufgabe vorsieht. Für die Formatierung von Zahlen ist dies beispielsweise NumberFormat.
import java.text.NumberFormat; import java.util.Locale; ... NumberFormat nf = NumberFormat.getNumberInstance(); International

1.

2.

Die Bemühungen, ein Programm so zu implementieren, dass es möglichst gut für den internationalen Markt vorbereitet ist, bezeichnet man als Internationalisierung, die Anpassung eines Programms an die nationalen Eigenheiten eines Landes als Lokalisierung. Beide verfolgen letzten Endes den gleichen Zweck und beruhen zum Teil auf identischen Techniken. Da Java intern Unicode zur Darstellung von Zeichen verwendet, kann es (grundsätzlich) sämtliche bekannten Zeichen verarbeiten. Die Reader- und Writer-Klassen von Java berücksichtigen automatisch die auf dem jeweiligen Rechner vorherrschende Zeichenkodierung und wandeln automatisch von dieser Kodierung in Unicode (Eingabe) oder umgekehrt (Ausgabe) um. Lediglich wenn Sie Daten einlesen, die anders kodiert sind, oder Textdaten in einer anderen Kodierung ausgeben wollen, müssen Sie auf InputStreamReader (bzw. OutputStreamReader) zurückgreifen, und die betreffende Zeichenkodierung explizit angeben (siehe Rezept 102). Achtung! Auch wenn Java alle Zeichen kodieren 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.

590 >> Lokale einstellen

Obige Zeile erzeugt eine NumberFormat-Instanz, die Zahlen gemäß der Standardlokale formatiert. Die Standardlokale ist die Lokale, die von allen Lokale-sensitiven Klassen/Methoden verwendet wird, wenn keine andere Lokale explizit angegeben wird. Beim Start der Anwendung setzt die Java Virtual Machine die aktuell auf dem System verwendete Lokale als Standardlokale der Anwendung ein. Zur Formatierung rufen Sie die Methode format() auf:
String formatted = nf.format(aNumber);

Sie erledigen die Aufgaben gemäß einer bestimmten, von Ihnen vorgegebenen Lokale. So erreichen Sie, dass Ihre Anwendung, unabhängig von dem System, auf dem sie ausgeführt wird, landes- und kulturspezifische Aufgaben immer nach Maßgabe einer festen Lokale erledigt. Hierzu gehen Sie wie im vorhergehenden Punkt vor, nur dass Sie zu Beginn der Anwendung Ihre eigene Lokale als Standardlokale einrichten:
import java.util.Locale; ... Locale.setDefault(new Locale("de", "DE"));

Sie erledigen einzelne Aufgaben gemäß einer bestimmten, von der Standardlokale abweichenden Lokale. Hierzu bedienen Sie sich ebenfalls der Lokale-sensitiven Klassen/Methoden, jedoch unter expliziter Angabe der zu verwendenden Lokale. Für die Formatierung von Zahlen mit NumberFormat sähe dies beispielsweise wie folgt aus:
import java.text.NumberFormat; import java.util.Locale; ... NumberFormat nf = NumberFormat.getNumberInstance(new Locale("de", "DE")); String formatted = nf.format(aNumber);

Locale-Objekte erzeugen Wenn Sie eine Aufgabe gemäß einer bestimmten Lokale erledigen möchten (siehe vorangehender Abschnitt oder nachfolgende Rezepte), müssen Sie für diese Lokale ein passendes Locale-Objekt erzeugen. Zur Identifizierung der Lokale übergeben Sie wahlweise den Sprachcode, Sprach- und Ländercode oder, in seltenen Fällen, Sprach-, Länder- und Umgebungscode.
Locale eigene = new Locale("de"); Locale eigene = new Locale("de","CH"); // Lokale für deutsch // Lokale für deutsch, Schweiz

International

Die Sprachcodes sind durch ISO-639 standardisiert, die Ländercodes durch ISO-3166. (Vorsicht! Die Standards verändern sich gelegentlich.)
Sprachcode ar da Sprache Arabisch Dänisch Ländercode LB DK Land Libanon Dänemark

Tabelle 53: Ausgesuchte Sprach- und Ländercodes

>> Internationalisierung

591

Sprachcode de

Sprache Deutsch

Ländercode DE CH AT

Land Deutschland Schweiz Österreich Griechenland Großbritannien USA Australien Kanada Spanien Kolumbien Frankreich Belgien Kanada Italien Japan Niederlande Norwegen Schweden Türkei China Hongkong Taiwan

el en

Griechisch Englisch

GR GB US AU CA

eo es fr

Esperanto Spanisch Französisch ES CO FR BE CA

it ja nl no sv tr zh

Italienisch Japanisch Niederländisch Norwegisch Schwedisch Türkisch Chinesisch

IT JP NL NO SE TR CN HK TW

Tabelle 53: Ausgesuchte Sprach- und Ländercodes (Forts.) International

Vollständige Listen finden Sie im Internet3. Sie können Sie sich aber auch selbst ausdrucken. Die Locale-Methoden getISOLanguages() und getISOCountries() liefern die Codes nach ISO639 und ISO-3166 zurück. Die Erzeugung einer eigenen Lokale instanziert lediglich ein Locale-Objekt, das die gewünschte Lokale im Quellcode repräsentiert. Die mit dieser Lokale verbundenen, Lokale-spezifischen Formatierungen sind nur verfügbar, wenn die Lokale von der installierten Java-Laufzeitumgebung (JRE) unterstützt wird (siehe Rezept 216). A c htu n g
3.

Für ISO-Sprachcodes beispielsweise http://www.loc.gov/standards/iso639-2/englangn.html. Für ISO-Ländercodes siehe http://www.iso.ch/iso/en/prods-services/iso3166ma/02iso-3166-code-lists/list-en1.html.

592 >> Standardlokale ändern

215 Standardlokale ändern
Die Lokale-sensitiven Klassen/Methoden der Java-API arbeiten – sofern Sie nicht explizit bei der Instanzierung der Klassen (bzw. Aufruf der Methoden) eine andere Lokale vorgeben – mit der Standardlokale der Anwendung. Die Standardlokale wird beim Start der Anwendung von der Java Virtual Machine initialisiert. Die Java Virtual Machine prüft dazu die Gebietsschemaeinstellungen des Betriebssystems, ermittelt die zugehörige Lokale und setzt diese als Standardlokale ein.

Standardlokale abfragen Falls Sie 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 (einige wenige Lokale-sensitiven Methoden arbeiten nicht automatisch mit der Standardlokale, sondern übernehmen die Lokale stets als Argument), so liefert Ihnen getDefault() die Standardlokale zurück:
import java.util.Locale; ... Locale loc = Locale.getDefault();

Standardlokale einstellen Wenn Sie möchten, dass Ihre Anwendung sprach- und länderspezifische Aufgaben stets gemäß ein und derselben Lokale erledigt, unabhängig davon, welche Lokale auf dem Rechner eingestellt ist, richten Sie die gewünschte Lokale zu Beginn des Programms als Standardlokale ein:
import java.util.Locale; ... Locale.setDefault(new Locale("de", "DE"));

International

A c h t u ng

Die ursprüngliche Standardlokale kann nach Einstellung einer anderen Standardlokale nicht mehr ermittelt werden, es sei denn, Sie speichern sie zuvor ab:
Locale save = Locale.getDefault(); Locale.setDefault(new Locale("de", "DE"));

Das Start-Programm zu diesem Rezept gibt den Namen der auf dem System installierten Lokale aus und verwendet sie zur Formatierung des aktuellen Datums. Dann ändert das Programm die Standardlokale (betrifft natürlich nur das Programm und nicht die landesspezifische Einstellung des Betriebssystems) und gibt das Datum erneut aus.
public class Start { public static void main(String args[]) { Date today = new Date(); DateFormat df; Listing 280: Standardlokale ändern

>> Internationalisierung

593

// Standardlokale = Lokale des Systems System.out.println("\n Aktuelle Lokale: " + Locale.getDefault() ); df = DateFormat.getDateInstance(DateFormat.LONG); System.out.println(" Aktuelles Datum: " + df.format(today)); // Standardlokale = fr_Fr für Frankreich Locale.setDefault(new Locale("fr", "FR")); System.out.println("\n Aktuelle Lokale: " + Locale.getDefault() ); df = DateFormat.getDateInstance(DateFormat.LONG); System.out.println(" Aktuelles Datum: " + df.format(today)); } } Listing 280: Standardlokale ändern (Forts.)

Abbildung 122: Datumsausgabe gemäß den Lokalen für Deutschland und Frankreich

216 Verfügbare Lokalen ermitteln
Die Erzeugung einer Lokale mit new Locale() instanziert lediglich ein Locale-Objekt, das die gewünschte Lokale im Quellcode repräsentiert. Die mit dieser Lokale verbundenen, Lokalespezifischen Formatierungen sind nur dann verfügbar, wenn eine entsprechende Lokale auf dem aktuellen Rechner (als Teil der Java-Laufzeitumgebung) installiert ist. Die Liste der garantiert unterstützten Lokalen ist nicht sehr lang. Sun fordert, dass jeder Provider von Java-Laufzeitumgebungen diese mit mindestens einer Lokale ausstattet: en_US (Englisch, USA). Die meisten JREs unterstützen natürlich weit mehr Lokalen, Suns JRE der Version 1.5.0 unterstützt gar über 100 Lokalen, von denen allerdings nur 21 ausführlich getestet sind (siehe Tabelle 54).
ID ar_SA zh_CN zh_TW nl_NL en_AU en_CA Sprache Arabic Chinese (Simplified) Chinese (Traditional) Dutch English English Land Saudi Arabia China Taiwan Netherlands Australia Canada International

Tabelle 54: Voll unterstützte und getestete Lokale der Sun-JRE

594 >> Verfügbare Lokalen ermitteln

ID en_GB en_US fr_CA fr_FR de_DE iw_IL hi_IN it_IT ja_JP ko_KR pt_BR es_ES sv_SE th_TH th_TH_TH

Sprache English English French French German Hebrew Hindi Italian Japanese Korean Portuguese Spanish Swedish Thai (Western digits) Thai (Thai digits)

Land United Kingdom United States Canada France Germany Israel India Italy Japan South Korea Brazil Spain Sweden Thailand Thailand

Tabelle 54: Voll unterstützte und getestete Lokale der Sun-JRE (Forts.)

Um sicherzustellen, dass eine Lokale, die Sie in einem Programm verwenden, auf den jeweiligen Systemen, auf denen das Programm ausgeführt wird, auch tatsächlich verfügbar ist, gibt es zwei Möglichkeiten:
International

A c h t u ng

Auf Windows-Systemen, die nur europäische Sprachen unterstützen, wird auch die JRE als rein europäische Version installiert.

Sie sorgen dafür, dass die Unterstützung für die Lokale zusammen mit Ihrem Programm installiert wird. Sie überprüfen im Programmcode, ob die Lokale von der auf dem aktuellen System installierten JRE unterstützt wird. Letzteres ist dank der statischen Locale-Methode getAvailableLocales() gar nicht so schwer:
Locale[] list = Locale.getAvailableLocales();

Die Methode liefert ein Array der auf dem System verfügbaren Lokalen zurück. Diese Liste brauchen Sie nur noch mit der von Ihnen gesuchten Lokale abzugleichen.
// Feststellen, ob Lokale unterstützt wird Locale requested = new Locale(de, DE); boolean found = false; Locale[] locs = Locale.getAvailableLocales(); Listing 281: Test auf Verfügbarkeit einer Lokale

>> Internationalisierung

595

for(Locale l : locs) { if(l.equals(requested)) { found = true; break; } } if(found) { // Lokale verwenden } else { // Andere Lokale verwenden, evt. Benutzer informieren } Listing 281: Test auf Verfügbarkeit einer Lokale (Forts.)

Zwei Punkte sind noch zu beachten: Eine installierte Lokale muss nicht alle Aspekte der Lokalisierung unterstützen! Es ist absolut zulässig, dass eine installierte Lokale lediglich die Formatierung von Zahlen oder das Vergleichen von Strings unterstützt. Die Lokale-sensitiven Klassen wie NumberFormat definieren daher eigene getAvailableLocales()-Methoden, die nur die Lokalen auflisten, die den entsprechenden Aspekt unterstützen. Es gibt aber auch eine gute Nachricht: Die von der Sun-JRE unterstützten Lokalen sind alle vollständig implementiert. Was tun Sie, wenn die gewünschte Lokale nicht verfügbar ist? Eine Möglichkeit ist, die nächstbeste Lokale auszuwählen. In diesem Fall können Sie unter Umständen sogar auf die Überprüfung mit getAvailableLocales() verzichten, denn die Lokale-sensitiven Klassen/Methoden gehen bereits nach diesem Verfahren vor. Sie ermitteln die »nächstbeste« Lokale durch schrittweisen Verzicht auf Umgebungs-, Länder- und Sprachcode. Letztes Refugium ist immer die Standardlokale. Angenommen, Sie fordern eine Lokale new Locale("de", "DE") an:
International

Ist keine de-Lokale mit dem Ländercode »DE« verfügbar, verwenden die Klassen/ Methoden die Lokale de. Ist auch keine Lokale de verfügbar, verwenden die Klassen/Methoden die Standardlokale. Die Standardlokale entspricht entweder der Lokale des aktuellen Systems oder – falls versucht wurde, die Standardlokale auf eine nicht unterstützte Lokale einzustellen – en_US. Die Sprach-, Länder- und Umgebungscodes einer mit new Locale() erzeugten Lokale werden nicht an die tatsächlich verfügbare Lokale-Unterstützung angepasst. Wenn Sie also eine Lokale new Locale("fr","FR") erzeugen, die installierte JRE jedoch keine Lokale für Französisch enthält und daher auf die Standardlokale ausweicht, wird die erzeugte Lokale vom Programm immer noch mit dem Sprachcode »fr« und dem Ländercode »FR« geführt. A cht un g

596 >> Verfügbare Lokalen ermitteln

Das Start-Programm zu diesem Rezept nimmt Sprach- und Ländercode der einzustellenden Standardlokale über die Befehlszeile entgegen. Ist die gewünschte Lokale verfügbar, wird sie eingerichtet, ansonsten wird eine Fehlermeldung auf die Konsole ausgegeben und die ursprüngliche Standardlokale beibehalten. Zum Schluss zeigt das Programm einen Meldungsdialog mit einem gemäß der Standardlokale formatierten Datum an.
import import import import java.util.Locale; java.util.Date; java.text.DateFormat; javax.swing.JOptionPane;

public class Start { public static void main(String args[]) { System.out.println(); if (args.length != 2) { System.out.println(" Aufruf: Start <Sprachcode> <Laendercode>"); System.exit(0); } Locale requested = new Locale(args[0], args[1]); // Feststellen, ob Lokale unterstützt wird boolean found = false; Locale[] locs = Locale.getAvailableLocales(); for(Locale l : locs) { if(l.equals(requested)) { found = true; break; } } if(found) { System.out.println(" Standardlokale wird umgestellt"); Locale.setDefault(requested); International } else { System.out.println(" Gewuenschte Lokale ist nicht verfuegbar."); System.out.println(" Standardlokale wird nicht geaendert."); } // Datum nach Standardlokale formatieren Date today = new Date(); DateFormat df = DateFormat.getDateInstance(DateFormat.LONG); String out = "Aktuelle Lokale: " + Locale.getDefault() + "\n\n" + df.format(today) + "\n\n"; javax.swing.JOptionPane.showMessageDialog(null, out); } } Listing 282: »Abgesicherte« Umstellung der Standardlokale

>> Internationalisierung

597

Abbildung 123: »Abgesicherte« Umstellung der Standardlokale

217 Lokale des Betriebssystems ändern
Die Lokalisierung von Java-Anwendungen beruht allein auf Mitteln der Sprache (namentlich der Unterstützung von Unicode und den Lokale-Klassen aus der Java-Laufzeitumgebung), ist also nicht auf Unterstützung seitens des Betriebssystems angewiesen. Der einzige Kontakt zu den Lokale-Informationen des Betriebssystems ist das Setzen der Standardlokale beim Start einer Anwendung. Aber auch hier gilt: Die Java Virtual Machine fragt die landesspezifische Konfiguration des Betriebssystems lediglich ab und wählt dann als Standardlokale die Lokale aus der JRE aus, die mit der Betriebssystemkonfiguration am besten übereinstimmt. H in we i s

Und Sie können Java-Anwendungen durch Umstellung der Standardlokale für Länder und Regionen schreiben und testen, die von Ihrem Entwicklungsrechner nicht unterstützt werden. Nichtsdestotrotz ist es natürlich ein Vorteil, wenn auch der Entwicklungsrechner für verschiedene Länder und Regionen konfiguriert werden kann: Sie können Anwendungen, die sich der landesspezifischen Konfiguration des Betriebssystems anpassen sollen, bequem für verschiedene Lokale testen. Sie können die Tastatur auf verschiedene Sprachen umstellen.

International

Sie können daher in Java – immer vorausgesetzt, die JRE unterstützt die betreffenden Lokalen – Anwendungen schreiben, die a) auf allen Betriebssystemen die gleiche Lokale verwenden, die b) sich der Konfiguration des Betriebssystems angleichen oder die c) unabhängig vom Betriebssystem vom Benutzer auf verschiedene Lokale umgestellt werden können.

Tipp

In dem Verzeichnis zu diesem Rezept finden Sie zudem ein Programm PrintLocales, mit dem Sie sich die Liste der verfügbaren Lokalen auf die Konsole ausgeben lassen können.

598 >> Lokale des Betriebssystems ändern

Wie Sie die Lokalen für Betriebssystem und Tastatur umstellen, hängt von dem jeweiligen Betriebssystem ab. Unter Windows XP/Vista wählen Sie in der Systemsteuerung die Option für Region- und Spracheinstellungen.

Abbildung 124: Einstellung eines Gebietsschemas (Lokale) unter Windows XP

Im Dialogfenster REGIONS- UND SPRACHOPTIONEN können Sie im oberen Listenfeld auf der Registerseite REGIONALE EINSTELLUNGEN (FORMATE unter Vista) die zu verwendende Lokale auswählen. Wenn Sie im gleichen Dialogfenster auf der Registerseite SPRACHEN die Schaltfläche DETAILS anklicken, gelangen Sie zum Textdienste-Dialog, wo Sie die Unterstützung für verschiedene Tastaturen installieren und auswählen können. Damit Sie bequem zwischen verschiedenen Tastaturen wechseln können, sollten Sie die EINGABEGEBIETSSCHEMA-LEISTE einblenden lassen bzw. die Tastaturen mit TASTATURKÜRZEL verbinden. H i n we i s

International

Unter Linux hängt der Weg zum Erfolg vom installierten Window Manager ab. Unter KDE 3.2 rufen Sie beispielsweise das Kontrollzentrum auf und gehen zu den Regionaleinstellungen. Auf der Seite LAND/REGION & SPRACHE können Sie dann die gewünschten Einstellungen vornehmen.

>> Internationalisierung

599

Abbildung 125: Regionaleinstellungen unter Linux/KDE 3.2

218 Strings vergleichen
Die Methode compareTo() vergleicht Strings anhand der Unicode-Werte der einzelnen Zeichen. Aus Sicht von compareTo() sind daher Kleinbuchstaben »größer« als Großbuchstaben und nationale Sonderzeichen (ä, ö, ß. é, è ...) ausnahmslos »größer« als die Buchstaben des lateinischen Alphabets. »Stäbe« käme im compareTo()-Lexikon also noch nach »Stube«, wo es wohl kein Mensch beim Nachschlagen finden würde. Wie aber kann man Strings alphabetisch korrekt vergleichen? Für Stringvergleiche unter Berücksichtigung der Besonderheiten eines nationalen Alphabets gibt es die Klasse Collator.
String str1 = "Stäbe"; String str2 = "Stube"; Collator coll = Collator.getInstance(new Locale("de")); if (coll.compare(str1, str2) < 0) // str1 < str2 else // str1 >= str2 Listing 283: Strings nach deutschem Alphabet vergleichen

International

600 >> Strings sortieren

1. Lassen Sie sich von Collator.getInstance() ein Collator-Objekt zurückliefern, welches gemäß dem gewünschten Alphabet (sprich Lokale) vergleicht. Wenn Sie wissen, von welcher Sprache die zu vergleichenden Strings sind, erzeugen Sie ein Locale-Objekt für diese Sprache und übergeben Sie es getInstance(), beispielsweise:
Collator coll = Collator.getInstance(new Locale("de"));

Wenn Sie die Sprache nicht kennen, aber davon ausgehen, dass es die Sprache des Benutzers ist, übergeben Sie keine Lokale. Die Methode verwendet dann die Standardlokale, die per Voreinstellung dem Gebietsschema des Betriebssystems entspricht.
Collator coll = Collator.getInstance();

2. Vergleichen Sie die Strings mit Hilfe der Collator-Methode compare(). Sie übergeben die beiden zu vergleichenden Strings und erhalten als Ergebnis -1, 0 oder 1 zurück, je nachdem, ob der erste String kleiner, gleich oder größer als der zweite String ist. Die Klasse Collator ist an sich abstrakt. Ihre getInstance()-Methode liefert ein Objekt einer abgeleiteten Klasse zurück. Beachten Sie außerdem, dass der Rückgabewert von Collator.compare() immer -1, 0 oder 1 lautet, während String.compareTo() die Differenz zwischen den Unicode-Werten der ersten abweichenden Zeichen (bzw. der Stringlängen) zurückliefert. Achtung
International

219 Strings sortieren
Am einfachsten sortieren Sie Strings mit Hilfe eines Arrays oder einer Collection-Instanz. Einige Collections speichern die eingefügten Elemente direkt in einer sortierten Reihenfolge (TreeMap, TreeSet), die restlichen Collections können mit Collections.sort(), Arrays mit Arrays.sort() sortiert werden. Wenn die eingefügten Elemente das Interface Comparable implementieren, können sie nach der Maßgabe ihrer compareTo()-Methode verglichen und sortiert werden. Alternativ kann ein Comparator-Objekt zum Sortieren der Elemente spezifiziert werden. Strings implementieren das Comparable-Interface, doch ihre compareTo()-Methode vergleicht allein anhand der Unicode-Werte ihrer Zeichen, ohne Berücksichtigung der Buchstabenfolge in nationalen Alphabeten. Um Strings alphabetisch korrekt zu sortieren, müssen Sie also auf den Einsatz eines Comparator-Objekts ausweichen. Glücklicherweise ist dies viel einfacher als man vielleicht annehmen würde, denn die Klasse Collator, die Strings gemäß einer Lokale vergleicht (siehe vorangehendes Rezept), implementiert dankenswerter Weise bereits für uns das Interface Comparator.

Arrays von Strings sortieren Arrays können Sie mit Hilfe der statischen sort()-Methode der Klasse Arrays sortieren. Als Parameter übergeben Sie das zu sortierende Array und – falls die Array-Elemente nicht das Comparable-Interface implementieren oder Sie wie in unserem Beispiel eine andere Sortierreihenfolge vorgeben möchten – ein Comparator-Objekt:
import java.util.Arrays; import java.text.Collator; ... String[] wordsArray = { "Stäbe", "Stube", "Stange", "Stoß", "Stottern" }; Arrays.sort(wordsArray, Collator.getInstance());

>> Internationalisierung

601

Collections von Strings sortieren Arrays können Sie mit Hilfe der statischen sort()-Methode der Klasse Collections sortieren. Als Parameter übergeben Sie die zu sortierende Collection und – falls die Collection-Elemente nicht das Comparable-Interface implementieren oder Sie wie in unserem Beispiel eine andere Sortierreihenfolge vorgeben möchten – ein Comparator-Objekt:
import java.util.LinkedList; import java.text.Collator; ... LinkedList<String> wordsList = new LinkedList<String>(); wordsList.add("Stäbe"); wordsList.add("Stube"); wordsList.add("Stange"); wordsList.add("Stoß"); wordsList.add("Stottern"); Collections.sort(wordsList, Collator.getInstance());

Sortierte Collections mit Strings als Elementen Die Collections TreeSet und TreeMap ordnen ihre Elemente bereits beim Einfügen in aufsteigender Reihenfolge an. Falls die Elemente nicht das Comparable-Interface implementieren oder Sie eine andere Sortierreihenfolge vorgeben möchten, müssen Sie das Comparator-Objekt daher bereits dem Konstruktor übergeben.
import java.util.TreeSet; import java.text.Collator; ... TreeSet<String> wordsTreeSet = new TreeSet<String>(Collator.getInstance()); wordsTreeSet.add("Stäbe"); wordsTreeSet.add("Stube"); wordsTreeSet.add("Stange"); wordsTreeSet.add("Stoß"); wordsTreeSet.add("Stottern");

Abbildung 126: Sortierte String-Sammlung

220 Datumsangaben parsen und formatieren
Datums- und Zeitangaben werden in Java durch Objekte der Klasse Date oder Calendar (genauer gesagt GregorianCalendar, siehe Rezept 39) repräsentiert und mit Hilfe von DateFormat bzw. SimpeDateFormat in formatierte Datum-/Zeitstrings umgewandelt, siehe Rezept 41.

International

602 >> Datumsangaben parsen und formatieren

Da die Klasse DateFormat und ihre abgeleiteten Klassen (SimpleDateFormat) Lokale-sensitiv sind, müssen Sie zur Lokalisierung Ihrer Datums- und Zeitangaben grundsätzlich nichts weiter tun, als die gewünschte Lokale mitzugeben. Den Methoden
getDateInstance() getTimeInstance() getDateTimeInstance()

übergeben Sie die Lokale als zweites (bzw. drittes) Argument hinter dem Formatierungsstil. Der Methode getInstance() können Sie keine Lokale übergeben, sie arbeitet immer mit der Standardlokale. Wenn Sie SimpleDateFormat explizit instanzieren, übergeben Sie die Lokale als zweites Argument hinter dem Formatstring. Wenn Sie keine Lokale übergeben, wird jeweils die Standardlokale herangezogen.
import java.util.Locale; import java.util.Calendar; import java.text.DateFormat; import java.text.SimpleDateFormat; ... Calendar today = Calendar.getInstance(); String dateTimeStr; // Formatierung mit vordefinierter DateFormat-Instanz DateFormat df = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.FULL, new Locale("en", "US")); dateTimeStr = df.format(today.getTime());

// Formatierung mit eigenem SimpleDateFormat-Objekt SimpleDateFormat sdf = new SimpleDateFormat("dd. MMMM yyyy', 'H:mm", new Locale("en", "US")); dateTimeStr = sdf.format(today.getTime()); International Listing 284: Datum und Zeit gemäß vorgegebener Lokale formatieren

Die Lokalisierung betrifft den allgemeinen Aufbau der Datums- und Zeitangaben sowie natürlich Textteile wie Monats- oder Wochentagsnamen, soweit sie eingebaut werden (vgl. Formatstile LONG und FULL).

Einlesen von Datums- und Zeitangaben Das Einlesen von Datums- und Zeitangaben wurde bereits in Rezept 43 behandelt, so dass ich hier nur kurz die Kernpunkte erwähne:
1. Sie erzeugen für das gewünschte Eingabeformat eine DateFormat-Instanz, 2. lesen die Datums-/Zeitangabe als String ein und 3. wandeln sie mit Hilfe der parse()-Methode der DateFormat-Instanz in ein Date-Objekt um.

>> Internationalisierung

603

import java.util.Calendar; import java.util.Locale; import java.text.DateFormat; import java.text.ParseException; ... Calendar date = Calendar.getInstance(); DateFormat parser = DateFormat.getDateInstance(DateFormat.MEDIUM, new Locale("de", "DE")); try { // Datum aus Befehlszeile einlesen date.setTime(parser.parse(args[0])); } catch(ParseException e) { System.err.println("\n Kein gueltiges Datum (TT.MM.JJJJ)"); System.err.println("\n Programm arbeitet mit aktuellem Datum."); } Listing 285: Datum und Zeit gemäß vorgegebener Lokale einlesen

Das Start-Programm zu diesem Rezept nimmt über die Befehlszeile ein Datum entgegen (Format TT.MM.JJJJ) und gibt es nach verschiedenen Lokalen formatiert aus (siehe Abbildung 127).

Abbildung 127: Datum- und Zeitangaben; die erste Zeile wurde jeweils mit einer getDateTimeInstance(DateFormat.LONG, DateFormat.FULL, Lokale)-Instanz, die zweite Zeile mit einer SimpleDateFormat(dd. MMMM yyyy', 'H:mm", Lokale)-Instanz formatiert.

International

604 >> Zahlen parsen und formatieren

221 Zahlen parsen und formatieren
Wenn Sie eine Zahl über die toString()-Methode der zugehörigen Wrapper-Klasse in einen String umwandeln lassen, wird dieser gemäß englischen Konventionen formatiert, also mit einem Komma als Tausendertrennzeichen und einem Punkt als Dezimalzeichen. Wenn Sie eine Zahl gemäß einer bestimmten Lokale formatierten wollen, müssen Sie den Zahlenwert mit Hilfe der Klasse NumberFormat bzw. ihrer abgeleiteten Klasse DecimalFormat umwandeln (siehe auch Rezept 8 zur formatierten Umwandlung von Zahlen in Strings). Den NumberFormat-Methoden
getInstance() getNumberInstance() getIntegerInstance() getPercentInstance()

übergeben Sie als Argument die Lokale, nach der der Zahlenwert formatiert werden soll. Wenn Sie DecimalFormat explizit instanzieren, übergeben Sie dem Konstruktor als zweites Argument eine lokalisierte DecimalFormatSymbols-Instanz. Wenn Sie keine Lokale spezifizieren, wird jeweils die Standardlokale herangezogen.
import import import import ... double String java.util.Locale; java.text.NumberFormat; java.text.DecimalFormat; java.text.DecimalFormatSymbols; number = 3344.588; numberStr;

// Formatierung mit vordefinierter NumberFormat-Instanz NumberFormat nf = NumberFormat.getNumberInstance(new Locale("de", "DE")); numberStr = nf.format(number);

International

// Formatierung mit eigenem DecimalFormat-Objekt DecimalFormat df = new DecimalFormat( "#,##0.00", new DecimalFormatSymbols(new Locale("en", "US"))); numberStr = df.format(number); Listing 286: Zahl gemäß vorgegebener Lokale formatieren

Einlesen von Zahlen Um Zahlen in einem landesspezifischen Format einzulesen, gehen Sie wie folgt vor:
1. Sie erzeugen für das gewünschte Zahlenformat eine NumberFormat-Instanz, 2. lesen die Zahl als String ein und 3. wandeln sie mit Hilfe der parse()-Methode der NumberFormat-Instanz in ein Number-Objekt um.

. } catch(ParseException e) { System.>> Internationalisierung 605 import import import . "DE")). die erste Zeile wurde jeweils mit einer getNumberInstance()-Instanz.println("\n Keine korrekte Zahlenangabe)"). nur dass Sie das Formatierer-Objekt über die Methode NumberFormat.err. Abbildung 128: Zahlenformatierungen. number.util.getNumberInstance(new Locale("de".Locale.getCurrencyInstance() anfordern oder im Pattern-Argument für den DecimalFormat-Konstruktur das Stellvertreterzeichen für das Währungssymbol (\u00A4) einbauen. double java. 222 Währungsangaben parsen und formatieren Für die lokalisierte Formatierung von Währungsangaben gilt grundsätzlich das Gleiche wie für die lokalisierte Formatierung von Zahlen. try { // Zahl aus Befehlszeile einlesen number = (parser. java.text.NumberFormat.ParseException..doubleValue().parse(args[0])). java. } Listing 287: Zahl gemäß vorgegebener Lokale einlesen Das Start-Programm zu diesem Rezept nimmt über die Befehlszeile eine Zahl entgegen (deutsches Format) und gibt sie nach verschiedenen Lokalen formatiert aus (siehe Abbildung 128). // Zahl einlesen NumberFormat parser = NumberFormat. International . die zweite Zeile mit einer DecimalFormatInstanz formatiert.text.

// Formatierung mit eigenem DecimalFormat-Objekt DecimalFormat df = new DecimalFormat( "#.text. java. // Formatierung mit vordefinierter NumberFormat-Instanz NumberFormat nf = NumberFormat.. Listing 288: Preisangabe gemäß vorgegebener Lokale formatieren Einlesen von Währungsangaben Hierfür gilt grundsätzlich das Gleiche wie für das Einlesen von Zahlen.getCurrencyInstance(new Locale("de". java.NumberFormat. "US"))).text.text. "DE")). number = 3344.Locale. new DecimalFormatSymbols(new Locale("en".DecimalFormatSymbols.util. currencyStr = nf.00 \u00A4 ". currencyStr.606 >> Währungsangaben parsen und formatieren import import import import .. International Abbildung 129: Formatierung von Währungsangaben. double String java. .588. siehe Rezept 221.##0. currencyStr = df. die zweite Zeile mit einer DecimalFormatInstanz formatiert.format(number).DecimalFormat.format(number). die erste Zeile wurde jeweils mit einer getCurrencyInstance()-Instanz. java.

Um einen Wert über mehrere Zeilen zu schreiben. Nicht-Latin-1-Zeichen können als Escape-Sequenzen (beispielsweise \u1234) in Schlüssel oder Werte eingebaut werden. Schlüssel und Wert dürfen Latin-1-Zeichen und Escape-Sequenzen (mit Ausnahme des Zeilenumbruchzeichens \n) enthalten.>> Internationalisierung 607 223 Ressourcendateien anlegen und verwenden Ressourcendateien sind ein probates Mittel. beenden Sie die Zeilen mit \. Schlüssel und Wert werden durch =. Über den Schlüssel kann das Programm später den Wert abfragen. Füllmuster etc. Führende Leerzeichen in der neuen Zeile werden (glücklicherweise) ignoriert. Der Wert beginnt mit dem ersten Nicht-Whitespace-Zeichen hinter dem Trennzeichen und reicht bis zum Ende der Zeile.) als externe Ressourcen auszulagern. Hintergründe.properties. Animationen. Leerzeichen sind nur als Escape-Sequenzen (\ ) erlaubt. Ressourcendateien haben immer die Extension .properties – Beispiel für eine Ressourcendatei International . jedes Whitespace-Zeichen außer dem Zeilenumbruchzeichen) getrennt. Texte von GUI-Elementen oder die Namen von Bilddateien (für Schaltersymbole.png # Schaltflaeche BTN_TITLE = Klick mich! # Dialog MDLG_TITLE = Nachricht MDLG_MESSAGE = Gut geklickt! Listing 289: Program. dem kein EscapeZeichen vorangestellt ist. Schlüssel müssen eindeutig sein und enden mit dem ersten Leerzeichen. Format Ressourcendateien sind letztlich Properties-Dateien und folgen daher dem gleichen Format: Die Ressourcen werden als Schlüssel/Wert-Paare zeilenweise in der Datei abgespeichert.properties # Hauptfenster MW_TITLE = Ressourcen-Demo MW_SYMBOL = resources/Germany. Hier die Ressourcendatei für das Programm zu diesem Rezept: # Program. um programminterne Daten wie Fehlermeldungen. Kommentare beginnen mit # oder !. : oder durch Leerzeichen (bzw.

} } International E x k ur s .exit(1). (Für relative Pfadangaben bedeutet dies üblicherweise. als Pfade zu den eigentlichen Ressourcen. Der folgende Code weist den Weg zu einer Ressourcendatei namens Program.println("Missing resource file"). Erzeugen Sie für die Ressourcendatei eine ResourceBundle-Instanz. Der Pfad ist daher so anzugeben. aus dem das Programm und die JVM gestartet wurde. dass Sie später jederzeit über den Namen der Klasse auf die Instanz und damit auf die Ressourcen zugreifen können.err. gehen Sie wie folgt vor: 1. .608 >> Ressourcendateien anlegen und verwenden Pfadangaben in Ressourcendateien Ressourcen wie Bilder. Class-Dateien können Sie in Ressourcendateien nur indirekt. die Instanz in der main()-Methode zu erzeugen und in einem statischen Feld der Klasse zu speichern.properties in einem Unterverzeichnis resources.) Ressourcen laden Um Ressourcen aus einer Ressourcendatei zu laden.. public static void main(String args[]) { // ResourceBundle aus Ressourcendatei laden try { resources = ResourceBundle.. Sound. dass das Programm als »Resource« aus der Ressourcendatei zuerst nur den Pfad lädt und über diesen dann auf die eigentliche Ressource zugreift. public class Start extends JFrame { public static ResourceBundle resources. Da Sie die ResourceBundle-Instanz vermutlich durch den gesamten Programmcode hindurch verwenden werden. Dabei ist zu beachten. } catch (MissingResourceException e) { System. dass sie sich auf das aktuelle Verzeichnis beziehen.. System. wie man ihn im Programmcode spezifizieren würde. So ist sichergestellt.. Der Name der Ressourcendatei wird dabei ohne Extension angegeben. empfiehlt es sich. angeben. Zur Erzeugung der Instanz rufen Sie die getBundle()-Methode von ResourceBundle auf und übergeben ihr den Pfad vom aktuellen Verzeichnis (von dem aus das Programm und die JVM gestartet werden) zur Ressourcendatei.getBundle("resources/Program"). } // Hauptfenster erzeugen und anzeigen .

>> Internationalisierung 609 Wenn Sie Ihre Klassen in Paketen definieren.*.util. sieht dies wie folgt aus: String s = Start.swing.event. javax. Der Wert der Ressource steht nach Ausführung der Methode in s. .awt.resources.resources. new ImageIcon(Start. oder kürzer JButton btn = new JButton("Klick mich". java. Start ist hierbei der Name der Programmklasse.Program H in we is auf.paketname.java International Das Start-Programm zu diesem Rezept lädt die Ressourcen aus der weiter oben abgedruckten Ressourcendatei Program. Das Icon für einen Schalter könnten Sie beispielsweise wie folgt laden: String path = Start.getString("MW_TITL