Das Java 6 Codebook
Das Java 6 Codebook
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 ver-
wendet 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
Printed in Germany
Textgestaltung
Inhaltsverzeichnis
Teil II Rezepte 17
Zahlen und Mathematik 19
Kapiteltext
1 Gerade Zahlen erkennen 19
2 Effizientes Multiplizieren (Dividieren) mit Potenzen von 2 19
3 Primzahlen erzeugen 20
4 Primzahlen erkennen 22
5 Gleitkommazahlen auf n Stellen runden 24
6 Gleitkommazahlen mit definierter Genauigkeit vergleichen 25
Kapiteltext
7 Strings in Zahlen umwandeln 26
8 Zahlen in Strings umwandeln 30
9 Ausgabe: Dezimalzahlen in Exponentialschreibweise 34
10 Ausgabe: Zahlenkolonnen am Dezimalzeichen ausrichten 37
11 Ausgabe in Ein- oder Mehrzahl (Kongruenz) 44
12 Umrechnung zwischen Zahlensystemen 46
13 Zahlen aus Strings extrahieren 49
Kapiteltext
14 Zufallszahlen erzeugen 51
15 Ganzzahlige Zufallszahlen aus einem bestimmten Bereich 53
16 Mehrere, nicht gleiche Zufallszahlen erzeugen (Lottozahlen) 54
17 Trigonometrische Funktionen 56
18 Temperaturwerte umrechnen (Celsius <-> Fahrenheit) 56
19 Fakultät berechnen 57
Kapiteltext
20 Mittelwert berechnen 59
21 Zinseszins berechnen 60
22 Komplexe Zahlen 62
23 Vektoren 72
24 Matrizen 78
25 Gleichungssysteme lösen 90
26 Große Zahlen beliebiger Genauigkeit 91
Kapiteltext
Strings 95
27 In Strings suchen 95
28 In Strings einfügen und ersetzen 98
29 Strings zerlegen 100
30 Strings zusammenfügen 101
Kapiteltext
System 179
Kapiteltext
Textgestaltung
70 INI-Dateien schreiben 187
71 INI-Dateien im XML-Format schreiben 188
72 Externe Programme ausführen 189
73 Verfügbaren Speicher abfragen 191
74 Speicher für JVM reservieren 192
Kapiteltext
82 Abbruch der Virtual Machine erkennen 206
83 Betriebssystem-Signale abfangen 208
Kapiteltext
87 Passwörter über die Konsole (Standardeingabe) lesen 216
88 Standardein- und -ausgabe umleiten 216
89 Konsolenanwendungen vorzeitig abbrechen 219
90 Fortschrittsanzeige für Konsolenanwendungen 219
91 Konsolenmenüs 222
92 Automatisch generierte Konsolenmenüs 225
Kapiteltext
93 Konsolenausgaben in Datei umleiten 230
94 Kommandozeilenargumente auswerten 230
95 Leere Verzeichnisse und Dateien anlegen 232
96 Datei- und Verzeichniseigenschaften abfragen 234
97 Temporäre Dateien anlegen 237
98 Verzeichnisinhalt auflisten 238
99 Dateien und Verzeichnisse löschen 239
Kapiteltext
GUI 287
113 GUI-Grundgerüst 287
114 Fenster (und Dialoge) zentrieren 292
115 Fenstergröße festlegen (und gegebenenfalls fixieren) 295
Beispiel für eine zwei-
Textgestaltung
154 Zeichnen mit unterschiedlichen Strichstärken und -stilen 431
155 Zeichnen mit Füllmuster und Farbverläufen 433
156 Zeichnen mit Transformationen 436
157 Verfügbare Schriftarten ermitteln 439
158 Dialog zur Schriftartenauswahl 440
Kapiteltext
166 Audiodateien abspielen 467
167 Videodateien abspielen 471
168 Torten-, Balken- und X-Y-Diagramme erstellen 475
Kapiteltext
171 Alle Treffer zurückgeben 486
172 Mit regulären Ausdrücken in Strings ersetzen 487
173 Anhand von regulären Ausdrücken zerlegen 488
174 Auf Zahlen prüfen 490
175 E-Mail-Adressen auf Gültigkeit prüfen 493
176 HTML-Tags entfernen 495
Kapiteltext
177 RegEx für verschiedene Daten 498
Datenbanken 501
178 Datenbankverbindung herstellen 501
179 Connection-Pooling 503
180 SQL-Befehle SELECT, INSERT, UPDATE und DELETE durchführen 505
181 Änderungen im ResultSet vornehmen 508
Kapiteltext
XML 565
Kapiteltext
Internationalisierung 589
Kapiteltext
Threads 623
228 Threads verwenden 623
229 Threads ohne Exception beenden 624
230 Eigenschaften des aktuellen Threads 627
231 Ermitteln aller laufenden Threads 628
Kapiteltext
>> Inhaltsverzeichnis 11
Textgestaltung
232 Priorität von Threads 629
233 Verwenden von Thread-Gruppen 631
234 Iterieren über Threads und Thread-Gruppen einer Thread-Gruppe 632
235 Threads in Swing: SwingWorker 635
236 Thread-Synchronisierung mit synchronized (Monitor) 639
Applets 655
Kapiteltext
242 Grundgerüst 655
243 Parameter von Webseite übernehmen 661
244 Bilder laden und Diashow erstellen 663
245 Sounds laden 669
246 Mit JavaScript auf Applet-Methoden zugreifen 672
247 Datenaustausch zwischen Applets einer Webseite 675
248 Laufschrift (Ticker) 677
Kapiteltext
Objekte, Collections, Design-Pattern 681
249 Objekte in Strings umwandeln – toString() überschreiben 681
250 Objekte kopieren – clone() überschreiben 683
251 Objekte und Hashing – hashCode() überschreiben 689
252 Objekte vergleichen – equals() überschreiben 695
Kapiteltext
253 Objekte vergleichen – Comparable implementieren 699
254 Objekte serialisieren und deserialisieren 705
255 Arrays in Collections umwandeln 710
256 Collections in Arrays umwandeln 711
257 Collections sortieren und durchsuchen 712
258 Collections synchronisieren 716
259 Design-Pattern: Singleton 718
Kapiteltext
Sonstiges 733
262 Arrays effizient kopieren 733
263 Arrays vergrößern oder verkleinern 734
Kapiteltext
Swing 782
Reguläre Ausdrücke 790
SQL 796
Lokale 797
Stichwortverzeichnis 811
Kapiteltext
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-to-
use 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äu-
tert. 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.
Kategorien Autor(en)
Zahlen und Mathematik Dirk Louis, [email protected]
Strings
Datum und Uhrzeit
System Peter Müller, [email protected]
Ein- und Ausgabe Peter Müller, [email protected]
GUI Dirk Louis, [email protected]
Grafik und Multimedia
Reguläre Ausdrücke und Pattern Matching Dirk Louis, [email protected]
Datenbanken Peter Müller, [email protected]
Netzwerke und E-Mail Peter Müller, [email protected]
XML
Internationalisierung Dirk Louis, [email protected]
Threads Peter Müller, [email protected]
Applets Dirk Louis, [email protected]
Objekte, Collections, Design-Pattern Dirk Louis, [email protected]
Sonstiges
Strings
GUI
RegEx
Datenbanken
XML
International
Threads
Applets
Objekte
Sonstiges
Zahlen
Zahlen und Mathematik
/**
* Stellt fest, ob die übergebene Zahl gerade oder ungerade ist
*/
public static boolean isEven(long number) {
return ((number & 1l) == 0l) ? true : false;
}
/**
* Multiplizieren mit Potenz von 2
*/
public static long mul(long number, int pos) {
return number << pos;
}
Listing 2: Multiplikation
Um eine Integer-Zahl durch 2n zu dividieren, muss man ihre Bits einfach nur um n Positionen
nach rechts verschieben:
/**
* Dividieren mit Potenz von 2
*/
public static long div(long number, int pos) {
return number >> pos;
}
Listing 3: Division
Beachten Sie, 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.
3 Primzahlen erzeugen
Primzahlen sind Zahlen, die nur durch sich selbst und durch 1 teilbar sind. 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, 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 beant-
wortet werden konnte (es gibt unendlich viele Primzahlen), erwiesen sich die anderen als Inspi-
ration und Herausforderung für Generationen von Mathematikern – und Informatikern. So
spielen Primzahlen beispielsweise bei der Verschlüsselung oder bei der Dimensionierung von
Hashtabellen eine große Rolle. 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, es aber andererseits unmöglich ist, in angemessener Zeit die Prim-
zahlen aus dem Produkt wieder herauszurechnen). Im zweiten Fall wird berücksichtigt, dass die
meisten Hashtabellen-Implementierungen Hashfunktionen1 verwenden, die besonders effizient
arbeiten, wenn die Kapazität der Hashtabelle eine Primzahl ist.
1. Hashtabellen speichern Daten als Schlüssel/Wert-Paare. Aufgabe der Hashfunktion ist es, aus dem Schlüssel den
Index zu berechnen, unter dem der Wert zu finden ist. Eine gute Hashfunktion liefert für jeden Schlüssel einen eige-
nen Index, der direkt zu dem gesuchten Wert führt. Weniger gute Hashfunktionen liefern für verschiedene Schlüssel
den gleichen Index, so dass hinter diesen Indizes Wertelisten stehen, die noch einmal extra durchsucht werden müs-
sen. In der Java-API werden Hashtabellen beispielsweise durch HashMap, HashSet oder Hashtable implementiert.
>> Zahlen und Mathematik 21
Zahlen
Der wohl bekannteste Algorithmus zur Erzeugung von Primzahlen ist das Sieb des Eratos-
thenes.
1. Schreibe alle Zahlen von 2 bis N auf.
2. Rahme die 2 ein und streiche alle Vielfachen von 2 durch.
3. Wiederhole Schritt 2 für alle n mit n <= sqrt(N), die noch nicht durchgestrichen wurden.
4. Alle eingerahmten oder nicht durchgestrichenen Zahlen sind Primzahlen.
Eine mögliche Implementierung dieses Algorithmus verwendet die nachfolgend definierte
Methode sieve(), die die Primzahlen aus einem durch min und max gegebenen Zahlenbereich
als LinkedList-Container zurückliefert.
import java.util.LinkedList;
...
/**
* Sieb des Eratosthenes
*/
public static LinkedList<Integer> sieve(int min, int max) {
if( (min < 0) || (max < 2) || (min > max) )
return null;
return prims;
}
Die Methode prüft zuerst, ob der angegebene Bereich überhaupt Primzahlen enthält. Dann legt
sie einen BitSet-Container zahlen an, der die Zahlen von 0 bis max repräsentiert. Anfangs sind
die Bits in numbers nicht gesetzt (false), was bedeutet, die Zahlen sind noch nicht ausgestri-
chen. In zwei verschachtelten for-Schleifen werden die Nicht-Primzahlen danach ausgestri-
chen. Zu guter Letzt werden die Primzahlen zwischen min und max in einen LinkedList-
Container übertragen und als Ergebnis zurückgeliefert.
22 >> Primzahlen erkennen
Zahlen
Wenn es Sie interessiert, welche Jahre im 20. Jahrhundert Primjahre waren, können Sie diese
Methode beispielsweise wie folgt aufrufen:
import java.util.LinkedList;
...
// Primzahlen erzeugen
LinkedList<Integer> prims = MoreMath.sieve(1900, 2000);
if(prims == null)
System.out.println("Fehler in Sieb-Aufruf");
else
for(int elem : prims)
System.out.print(" " + elem);
Wenn Sie in einem Programm einen Hashtabellen-Container anlegen wollen, dessen Anfangs-
kapazität sich erst zur Laufzeit ergibt, benötigen Sie allerdings eine Methode, die ihnen genau
eine passende Primzahl zurückliefert. Dies leistet die Methode getPrim(). Sie übergeben der
Methode die gewünschte Mindestanfangskapazität und erhalten die nächsthöhere Primzahl
zurück.
import java.util.LinkedList;
...
/**
* Liefert die nächsthöhere Primzahl zurück
*/
public static int getPrim(int min) {
LinkedList<Integer> l;
int max = min + 20;
do {
l = sieve(min, max);
max += 10;
} while (l.size() == 0);
return l.getFirst();
}
Die Erzeugung eines HashMap-Containers mit Hilfe von getPrim() könnte wie folgt aussehen:
java.util.HashMap map = new java.util.HashMap(MoreMath.getPrim(min));
4 Primzahlen erkennen
Das Erkennen von Primzahlen ist eine Wissenschaft für sich – und ein Gebiet, auf dem sich
vor kurzem (im Jahre 2001) Erstaunliches getan hat.
Kleinere Zahlen, also Zahlen < 9.223.372.036.854.775.807 (worin Sie unschwer die größte
positive long-Zahl erkennen werden), lassen sich schnell und effizient ermitteln, indem man
prüft, ob sie durch irgendeine kleinere Zahl (ohne Rest) geteilt werden können.
>> Zahlen und Mathematik 23
Zahlen
/**
* Stellt fest, ob eine long-Zahl eine Primzahl ist
*/
public static boolean isPrim(long n) {
return true;
}
Die Methode testet zuerst, ob die zu prüfende Zahl n kleiner als 2 oder gerade ist. Wenn ja, ist
die Methode fertig und liefert false zurück. Hat n die ersten Tests überstanden, geht die
Methode in einer Schleife alle ungeraden Zahlen bis sqrt(n) durch und probiert, ob n durch
die Schleifenvariable i ohne Rest geteilt werden kann. Gibt es einen Teiler, ist n keine Primzahl
und die Methode kehrt sofort mit dem Rückgabewert false zurück. Gibt es keinen Teiler, liefert
die Methode true zurück.
Leider können wegen der Beschränkung des Datentyps long mit dieser Methode nur relativ
kleine Zahlen getestet werden. Um größere Zahlen zu testen, könnte man den obigen Algo-
rithmus für die Klasse BigInteger implementieren. (BigInteger und BigDecimal erlauben die
Arbeit mit beliebig großen (genauen) Integer- bzw. Gleitkommazahlen, siehe Rezept 26.) In der
Praxis ist dieser Weg aber kaum akzeptabel, denn abgesehen davon, dass die BigInteger-
Modulo-Operation recht zeitraubend ist, besitzt der Algorithmus wegen der Modulo-Operation
in der Schleife von vornherein ein ungünstiges Laufzeitverhalten.
Mangels schneller deterministischer Verfahren werden Primzahlen daher häufig mit Hilfe pro-
babilistischer Verfahren überprüft, wie zum Beispiel dem Primtest nach Rabin-Miller.
So verfügt die Klasse BigInteger über eine Methode isProbablePrime(), mit der Sie BigInte-
ger-Zahlen testen können. Liefert die Methode false zurück, handelt es sich definitiv um
keine Primzahl. Liefert die Methode true zurück, liegt die Wahrscheinlichkeit, dass es sich um
eine Primzahl handelt, bei 1 – 1/2n. Den Wert n übergeben Sie der Methode als Argument.
(Die Methode testet intern nach Rabin-Miller und Lucas-Lehmer.)
import java.math.BigInteger;
/**
* Stellt fest, ob eine BigInteger-Zahl eine Primzahl ist
* (erkennt Primzahl mit nahezu 100%-iger Sicherheit (1 - 1/28 = 0,9961))
*/
24 >> Gleitkommazahlen auf n Stellen runden
Zahlen
Rundungsmethode Beschreibung
Cast () Rundet immer ab.
Math.rint(double x) Rundet mathematisch (Rückgabetyp double).
Math.round(double x) Rundet kaufmännisch (Rückgabetyp long bzw. int).
Math.round(float x)
Math.ceil(double x) Rundet auf die nächste größere ganze Zahl auf (Rückgabetyp double).
Math.floor(double x) Rundet auf die nächste kleinere ganze Zahl ab (Rückgabetyp double).
Tabelle 1: Rundungsmethoden
Möchte man Dezimalzahlen auf Dezimalzahlen mit einer bestimmten Anzahl Nachkommastel-
len runden, bedarf es dazu eigener Methoden: eine zum mathematischen und eine zum kauf-
männischen Runden auf x Stellen.
Das kaufmännische Runden betrachtet lediglich die erste zu rundende Stelle. Ist diese gleich 0,
1, 2, 3 oder 4, wird ab-, ansonsten aufgerundet. Dieses Verfahren ist einfach, führt aber zu
einer gewissen Unausgewogenheit, da mehr Zahlen auf- als abgerundet werden. Das mathe-
matische Runden rundet immer von hinten nach vorne und unterscheidet sich in der Behand-
lung der 5 als zu rundender Zahl:
왘 Folgen auf die 5 noch weitere von 0 verschiedene Ziffern, wird aufgerundet.
왘 Ist die 5 durch Abrundung entstanden, wird aufgerundet. Ist sie durch Aufrundung ent-
standen, wird abgerundet.
왘 Folgen auf die 5 keine weiteren Ziffern, wird so gerundet, dass die vorangehende Ziffer
gerade wird.
Der letzte Punkt führt beispielsweise dazu, dass die Zahl 8.5 von rint() auf 8 abgerundet wird,
während sie beim kaufmännischen Runden mit round() auf 9 aufgerundet wird.
Mit den folgenden Methoden können Sie kaufmännisch bzw. mathematisch auf n Stellen
genau runden.
/**
* Kaufmännisches Runden auf n Stellen
*/
public static double round(double number, int n) {
return (Math.round(number * Math.pow(10,n))) / Math.pow(10,n);
}
>> Zahlen und Mathematik 25
Zahlen
/**
* Mathematisches Runden auf n Stellen
*/
public static double rint(double number, int n) {
return (Math.rint(number * Math.pow(10,n))) / Math.pow(10,n);
}
Die Methoden multiplizieren die zu rundende Zahl mit 10n, um die gewünschte Anzahl Nach-
kommastellen zu erhalten, runden das Ergebnis mit round() bzw. rint() und dividieren das
Ergebnis anschließend durch 10n, um wieder die alte Größenordnung herzustellen.
if (number == 0.0)
System.out.println("gleich Null");
Dabei weicht number nur minimal von 0.0 ab! Um mit Rundungsfehlern behaftete Gleitkomma-
zahlen korrekt zu vergleichen, bedarf es daher einer Vergleichsfunktion, die mit einer gewis-
sen Toleranz (epsilon) arbeitet:
26 >> Strings in Zahlen umwandeln
Zahlen
/**
* Gleitkommazahlen mit definierter Genauigkeit vergleichen
*/
public static boolean equals(double a, double b, double eps) {
return Math.abs(a - b) < eps;
}
Mit dieser Methode kann die »Gleichheit« wie gewünscht festgestellt werden:
double number = 12.123456;
number -= 12.0;
number -= 0.123456;
Apropos Vergleiche und Gleitkommazahlen: Denken Sie daran, dass Sie Vergleiche
Achtung
Zahlen
왘 Die parse()-Methode von DecimalFormat
Die Klasse DecimalFormat wird zwar vorzugsweise zur formatierten Umwandlung von Zah-
len in Strings verwendet (siehe Rezept 8), mit ihrer parse()-Methode kann aber auch der
umgekehrte Weg eingeschlagen werden.
Die parse()-Methode parst die Zeichen im übergebenen String so lange, bis sie auf ein
Zeichen trifft, das sie nicht als Teil der Zahl interpretiert (Buchstabe, Satzzeichen). Aber
Achtung! Das Dezimalzeichen, gemäß der voreingestellten Lokale der Punkt, wird igno-
riert. Die eingeparsten Zeichen werden in eine Zahl umgewandelt und als Long-Objekt
zurückgeliefert. Ist der Zahlenwert zu groß oder wurde zuvor für das DecimalFormat-Objekt
setParseBigDecimal(true) aufgerufen, wird das Ergebnis als Double-Objekt zurückgeliefert.
Kann keine Zahl zurückgeliefert werden, etwa weil der String mit einem Buchstaben
beginnt, wird eine ParseException ausgelöst.
Der Rückgabetyp ist in jedem Fall Number. Mit den Konvertierungsmethoden von Number
(toInt(), toDouble() etc.) kann ein passender elementarer Typ erzeugt werden.
Die parse()-Methode ist überladen. Die von NumberFormat geerbte Version übernimmt
allein den umzuwandelnden String, die in DecimalFormat definierte Version erhält als zwei-
tes Argument eine Positionsangabe vom Typ ParsePosition, die festlegt, ab wo mit dem
Parsen des Strings begonnen werden soll.
import java.text.DecimalFormat;
import java.text.ParseException;
Mit dem folgenden Programm können Sie Verhalten und Laufzeit der verschiedenen Umwand-
lungsmethoden auf Ihrem Rechner prüfen:
import java.util.Scanner;
import java.text.DecimalFormat;
import java.text.ParseException;
if (args.length != 1) {
System.out.println(" Aufruf: Start <Ganzzahl>");
Zahlen
System.exit(0);
}
}
}
Zahlen
int number = 12;
System.out.print(number);
System.out.print("Wert der Variablen: " + number);
Symbol Bedeutung
0 obligatorische Ziffer
# optionale Ziffer
. Dezimalzeichen
, Tausenderzeichen
- Minuszeichen
E Exponentialzeichen
#,##0.00#
Die Vorkommastellen können durch das Tausenderzeichen gruppiert werden. Es ist unnötig,
mehr als ein Tausenderzeichen zu setzen, 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). Im Vorkommateil darf rechts von einer
obligatorischen Ziffer (0) keine optionale Ziffer mehr folgen. Die maximale Anzahl Stellen im
Vorkommateil ist unbegrenzt, optionale Stellen müssen lediglich zum Setzen des Tausender-
zeichens angegeben werden. Im Nachkommateil darf rechts von einer optionalen Ziffer keine
obligatorische Ziffer mehr folgen. Die Zahl der obligatorischen Ziffern entspricht hier der Min-
destzahl an Stellen, die Summe aus obligatorischen und optionalen Ziffern der Maximalzahl an
Stellen. Optional kann sich an beide Formate die Angabe eines Exponenten anschließen (siehe
Rezept 9).
Negative Zahlen werden standardmäßig durch Voranstellung des Minuszeichens gebildet, es
sei denn, es wird dem Pattern für die positiven Zahlen mittels ; ein spezielles Negativ-Pattern
angehängt.
32 >> Zahlen in Strings umwandeln
Zahlen
NumberFormat-Methode Liefert
getInstance() Format für beliebige Zahlen:
getNumberInstance() #,##0.###
(= Zahl mit mindestens einer Stelle, maximal drei Nachkomma-
stellen und Tausenderzeichen nach je drei Stellen)
getIntegerInstance() Format für Integer-Zahlen:
#,##0
(= Zahl mit mindestens einer Stelle, keine Nachkommastellen und
Tausenderzeichen nach je drei Stellen)
getPercentInstance() Format für Prozentangaben:
#,##0%
(= Zahl mit mindestens einer Stelle, keine Nachkommastellen,
Tausenderzeichen nach je drei Stellen und abschließendem Prozent-
zeichen)
(Achtung! Die zu formatierende Zahl wird automatisch mit 100
multipliziert.)
getCurrencyInstance() Format für Preisangaben:
#,##0.00 ¤
(= Zahl mit mindestens einer Stelle, genau zwei Nachkommastellen,
Tausenderzeichen nach je drei Stellen und abschließendem Leer- und
Währungszeichen)
import java.text.NumberFormat;
...
number = 12345.6789;
nf = NumberFormat.getNumberInstance();
System.out.print(nf.format(number)); // Ausgabe: 12.345,679
>> Zahlen und Mathematik 33
Zahlen
Formatierungsobjekte anpassen
Die von einem DecimalFormat-Objekt vorgenommene Formatierung kann jederzeit durch Auf-
ruf der entsprechenden set-Methoden angepasst werden.
Methode Beschreibung
void setCurrency(Currency c) Ändert die zu verwendende Währung.
void setDecimalSeparatorAlwaysShown(boolean opt) Wird true übergeben, wird das Dezimalzeichen
auch am Ende von Integer-Zahlen angezeigt.
void setGroupingSize(int n) Anzahl Stellen pro Gruppe.
void setGroupingUsed(boolean opt) Legt fest, ob die Vorkommastellen gruppiert
werden sollen.
void setMaximumFractionDigits(int n) Maximale Anzahl Stellen im Nachkommateil.
void setMaximumIntegerDigits(int n) Maximale Anzahl Stellen im Integer-Teil.
void setMinimumFractionDigits(int n) Minimale Anzahl Stellen im Nachkommateil.
void setMinimumIntegerDigits(int n) Minimale Anzahl Stellen im Integer-Teil.
void setMultiplier(int n) Faktor für Prozent- und Promille-Darstellung.
setNegativePrefix(String new) Setzt Präfixe und Suffixe der Patterns für
setNegativeSuffix(String new) negative bzw. positive Zahlen.
setPositivePrefix(String new)
setPositiveSuffix(String new)
Tabelle 6: Set-Methoden von DecimalFormat (die hervorgehobenen Methoden sind auch für
NumberFormat definiert)
import java.text.DecimalFormat;
import java.text.NumberFormat;
...
static {
NFUS = NumberFormat.getNumberInstance(Locale.US);
NFUS_NOGROUP = NumberFormat.getNumberInstance(Locale.US);
NFUS_NOGROUP.setGroupingUsed(false);
}
// Instanzbildung unterbinden
private MoreMath() { }
}
Aufruf:
public static void main(String args[]) {
...
double number = 12345.6789
System.out.println(MoreMath.NFUS.format(number));
System.out.println(MoreMath.NFUS_NOGROUP.format(number));
}
Ausgabe:
12,345.679
12345.679
Zahlen
Die Umwandlung eines solchen Patterns in eine formatierte Zahl ist etwas eigentümlich.
Grundsätzlich gilt: Sie geben die maximale und minimale Anzahl Vorkommastellen an und
das DecimalFormat-Objekt berechnet den passenden Exponenten. Aus diesem Grund kann für
den Exponenten auch nur die minimale Anzahl Stellen angegeben werden. Die maximale Zahl
ist unbeschränkt.
Für die Berechnung des Exponenten gibt es zwei Modelle, die über den Aufbau des Vorkom-
mateils ausgewählt werden:
왘 Besteht der Vorkommateil nur aus obligatorischen Stellen (0), berechnet DecimalFormat den
Exponenten so, dass exakt die vorgegebene Zahl Stellen vor dem Komma erreicht wird.
왘 Enthält der Vorkommateil optionale Stellen (#), ist der Exponent stets ein Vielfaches der
Summe an Vorkommastellen.
Die Anzahl signifikanter Stellen in der Mantisse entspricht der Summe aus obligatorischen
Vorkomma- und maximaler Anzahl Nachkommastellen.
Tausenderzeichen sind nicht erlaubt.
Vordefinierte Patterns
Wenn Sie an verschiedenen Stellen immer wieder dieselben Formatierungen benötigen, lohnt
sich unter Umständen die Definition eigener vordefinierter Formate, beispielsweise in Form
von static final-Konstanten, die mittels eines static-Blocks konfiguriert werden.
Die folgenden Definitionen gestatten die schnelle Formatierung von Gleitkommazahlen in
Exponentialschreibweise mit
왘 einer Vorkommastelle und sechs signifikanten Stellen (DFEXP),
왘 sechs signifikanten Stellen und Exponenten, die Vielfache von 3 sind (DFEXP_ENG).
import java.text.DecimalFormat;
static {
DFEXP = new DecimalFormat("0.#####E0");
DFEXP_ENG = new DecimalFormat("##0.#####E0");
}
...
}
Eigene Formatierungsmethode
DecimalFormat verwendet zur Kennzeichnung des Exponenten ein großes E und zeigt positive
Exponenten ohne Vorzeichen an. Wer ein kleines E bevorzugt oder den Exponenten stets mit
Vorzeichen dargestellt haben möchte (so wie es printf() tut), muss den von Decimal-
Format.format() zurückgelieferten String manuell weiterverarbeiten.
Die Methode formatExp() kann Ihnen diese Arbeit abnehmen. Sie formatiert die übergebene
Zahl in Exponentialschreibweise mit einer Vorkommastelle. Die maximale Anzahl Nachkom-
mastellen in der Mantisse wird als Argument übergeben. Optional können Sie über Boolesche
Argumente zwischen großem und kleinem E und zwischen Plus- und Minuszeichen oder nur
Minuszeichen vor dem Exponenten wählen.
import java.text.DecimalFormat;
/**
* Formatierung als Dezimalzahl in Exponentialschreibweise
*/
public static String formatExp(double number, int maxStellen) {
return MoreMath.formatExp(number, maxStellen, false, false);
}
if(maxDigits > 1)
pattern.append(MoreString.charNTimes('#',maxDigits-1));
pattern.append("E00");
Zahlen
// Zahl als String formatieren
String str = (new DecimalFormat(pattern.toString())).format(number);
if (smallExp)
tmp.replace(pos, pos+1, "e");
return tmp.toString();
} else
return str;
}
}
Die Methode formatExp(double number, int maxStellen, boolean smallExp, boolean plus)
baut zuerst das gewünschte Pattern auf, wobei sie zur Vervielfachung der optionalen Nach-
kommastellen die Methode MoreString.charNTimes() aus Rezept 32 aufruft. Dann erzeugt sie
den gewünschten Formatierer und noch in der gleichen Zeile durch Aufruf der format()-
Methode die Stringdarstellung der Zahl. Für eine Darstellung mit kleinem e wird das große E
im String durch das kleine e ersetzt. Wurde die Darstellung mit Pluszeichen vor dem Exponent
gewünscht und ist der Betrag der Zahl größer oder gleich 1, wird das Pluszeichen hinter dem E
(bzw. e) eingefügt.
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, dass bei String-
Manipulationen stets Kopien erzeugt werden), sondern wegen der insert()-Methode, die
String fehlt.
Mit dem zugehörigen Testprogramm können Sie das Ergebnis verschiedener Formatierungs-
möglichkeiten vergleichen.
Format (bzw. DecimalFormat) lösen lässt. Wie dies konkret aussieht, untersuchen die beiden fol-
genden Abschnitte.
import java.text.DecimalFormat;
import java.text.FieldPosition;
...
/**
* Array von Strings am Dezimalzeichen ausrichten
Zahlen
* (Version für proportionale Schrift)
*/
public static StringBuffer[] alignAtDecimal (double[] numbers) {
return alignAtDecimalPoint(numbers, "#,##0.00");
}
// nötige Vorarbeiten
// Strings initisieren, Position des Dezimalpunkts
// feststellen, max. Zahl Vorkommastellen ermitteln
for(int i = 0; i < numbers.length; ++i) {
strings[i] = new StringBuffer("");
df.format(numbers[i], strings[i], fpos);
charToDecP[i] = fpos.getEndIndex();
if (maxDist < charToDecP[i])
maxDist = charToDecP[i];
}
strings[i].insert(0, pad);
}
return strings;
}
Wie findet diese Methode die Position der Dezimalzeichen? Denkbar wäre natürlich, einfach
mit indexOf() nach dem Komma zu suchen. Doch dieser Ansatz funktioniert natürlich nur,
wenn DecimalFormat gemäß einer Lokale formatiert, die das Komma als Dezimalzeichen ver-
wendet. Lauten die Alternativen demnach, entweder eigenen Code zur Unterstützung ver-
schiedener Lokale zu schreiben oder aber eine feste Lokale vorzugeben und damit auf
automatische Adaption an nationale Eigenheiten zu verzichten? Mitnichten. 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. Für die
alignAtDecimal()-Methode sieht dies so aus, dass diese eingangs ein FieldPosition-Objekt
erzeugt. Dieses liefert Informationen über den ganzzahligen Anteil, zu welchem Zweck die
Konstante INTEGER_FIELD übergeben wird. (Wenn Sie die ebenfalls vordefinierte Konstante
40 >> Ausgabe: Zahlenkolonnen am Dezimalzeichen ausrichten
Zahlen
FRACTION_FIELD übergeben, beziehen sich die Angaben, die die Methoden des FieldPosition-
Objekts zurückliefern, auf den Nachkommaanteil.)
In einer ersten Schleife werden dann die Zahlen mit Hilfe der format()-Methode in Strings
umgewandelt und in StringBuffer-Objekten abgespeichert. Die Position des Dezimalzeichens
wird für jeden String mit Hilfe der FieldPosition-Methode getEndIndex() abgefragt und im
Array charToDecP zwischengespeichert. (Enthält der String kein Dezimalzeichen, wird die Posi-
tion hinter der letzten Ziffer des Vorkommateils zurückgeliefert.) Gleichzeitig wird in maxDist
der größte Abstand von Stringanfang bis Dezimalzeichen festgehalten.
In der anschließenden, zweiten for-Schleife werden die Strings dann so weit vorne mit Leer-
zeichen aufgefüllt, dass in allen Strings das Dezimalzeichen maxDist Positionen hinter dem
Stringanfang liegt.
Der Einsatz der Methode könnte nicht einfacher sein: Sie übergeben ihr das Array der zu for-
matierenden Zahlen und erhalten die fertigen Strings in Form eines StringBuffer-Arrays
zurück:
// aus Start.java
double[] numbers = { 1230.45, 100, 8.1271 };
StringBuffer[] strings;
strings = MoreMath.alignAtDecimal(numbers);
System.out.println();
System.out.println(" Kapital : " + strings[0]);
System.out.println(" Bonus : " + strings[1]);
System.out.println(" Rendite (%) : " + strings[2]);
Sagt Ihnen die vorgegebene Formatierung mit zwei Nachkommastellen nicht zu, übergeben
Sie einfach Ihren eigenen Formatstring, siehe auch Rezept 8.
// aus Start.java
strings = MoreMath.alignAtDecimal(numbers, "#,##0.0######");
System.out.println();
System.out.println(" Kapital : " + strings[0]);
System.out.println(" Bonus : " + strings[1]);
System.out.println(" Rendite (%) : " + strings[2]);
Zahlen
Abbildung 3: Ausgerichtete Zahlenkolonnen (Formate: »#,##0.00« (Vorgabe der überladenen
alignAtDecimal()-Version), »#,##0.0######« und »#,##0.##«)
Statt die aufbereiteten Stringdarstellungen auf die Konsole auszugeben, können Sie sie
H inwe is
int x = 50;
int y = 50;
FontMetrics fm;
import java.text.DecimalFormat;
import java.text.FieldPosition;
import java.awt.FontMetrics;
/**
* Array von Strings am Dezimalzeichen ausrichten
if(numbers.length != xOffsets.length)
throw new IllegalArgumentException("Fehler in Array-Dimensionen");
// nötige Vorarbeiten
// Strings erzeugen, Position des Dezimalpunkts
// feststellen, Pixelbreite bis Dezimalpunkt ermitteln
for(int i = 0; i < numbers.length; ++i) {
strings[i] = new StringBuffer("");
df.format(numbers[i], strings[i], fpos);
pixToDecP[i] = fm.stringWidth(strings[i].substring(0,
fpos.getEndIndex()));
if (maxDist < pixToDecP[i])
maxDist = pixToDecP[i];
}
// xOffsets berechnen
for(int i = 0; i < numbers.length; ++i) {
xOffsets[i] = maxDist - pixToDecP[i];
}
return strings;
}
Für die Ausrichtung von Zahlen in nichtproportionaler Schrift übernimmt die alignAtDeci-
mal()-Methode zwei weitere Argumente:
왘 Zum einen muss sie für jeden formatierten String den Abstand vom Stringanfang bis zum
Dezimalzeichen in Pixeln berechnen. Da sie dies nicht allein leisten kann, übernimmt sie
ein FontMetrics-Objekt, das zuvor für den gewünschten Ausgabefont erzeugt wurde (siehe
>> Zahlen und Mathematik 43
Zahlen
Listing 12). Deren stringWidth()-Methode übergibt sie den Teilstring vom Stringanfang
bis zum Dezimalzeichen und erhält als Ergebnis die Breite in Pixel zurück, die sie im Array
pixToDecP speichert.
왘 Neben den formatierten Strings muss die Methode dem Aufrufer für jeden String den x-
Offset übergeben, um den die Ausgabe verschoben werden muss, damit die Dezimalzeichen
untereinander liegen. Zu diesem Zweck übernimmt die Methode ein int-Array, in dem es
die Offsetwerte abspeichert. 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.
Im folgenden Beispiel werden letzten Endes zwei Spalten ausgegeben. Die erste Spalte besteht
aus den Strings des Arrays prefix und wird rechtsbündig ausgegeben. Die zweite Spalte ent-
hält die Zahlen des Arrays numbers, die am Dezimalzeichen ausgerichtet werden sollen. Um
dies zu erreichen, berechnet das Programm die Länge des größten Strings der 1. Spalte (fest-
gehalten in prefixlength) sowie mit Hilfe von alignAtDecimal() die x-Verschiebungen für die
Strings der zweiten Spalte.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
int x = 50;
int y = 50;
FontMetrics fm;
y + i*fm.getHeight() );
}
}
} // Ende von MyCanvas
...
Die Strings der ersten Spalte werden einfach mit drawString() an der X-Koordinate x gezeich-
net. 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 kom-
men) nach rechts verschoben.
Zahlen
oder noch schlimmer:
Sie haben 1 Brote bestellt.
Die meisten Programmierer lösen dieses Problem, indem sie das Substantiv in Ein- und Mehr-
zahl angeben – in diesem Fall also Brot(e) – oder die verschiedenen Fälle durch if-Verzwei-
gungen unterscheiden. Darüber hinaus gibt es in Java aber noch eine eigene Klasse, die
speziell für solche (und noch kompliziertere) Fälle gedacht ist: java.text.ChoiceFormat.
ChoiceFormat-Instanzen bilden eine Gruppe von Zahlenbereichen auf eine Gruppe von Strings
ab. Die Zahlenbereiche werden als ein Array von double-Werten definiert. Angegeben wird
jeweils der erste Wert im Zahlenbereich. So definiert das Array
double[] limits = {0, 1, 2};
die Zahlenbereiche
[0, 1)
[1, 2)
[2, ∞)
Werte, die kleiner als der erste Bereich sind, werden diesem zugesprochen.
Als Pendant zum Bereichsarray muss ein String-Array definiert werden, das ebenso viele
Strings enthält, wie es Bereiche gibt (hier also drei):
String[] outputs = {"Brote", "Brot", "Brote"};
Übergibt man beide Arrays einem ChoiceFormat-Konstruktor, erzeugt man eine Abbildung der
Zahlen aus den angegebenen Bereichen auf die Strings:
ChoiceFormat cf = new ChoiceFormat(limits, outputs);
Der Ausgabestring für unsere Online-Bäckerei lautete damit:
"Sie haben " + countBreads + " " + cf.format(countBreads) + " bestellt.\n"
und würde für 0, 1, 2 und 3 folgende Ausgaben erzeugen:
Sie haben 0 Brote bestellt.
Sie haben 1 Brot bestellt.
Sie haben 2 Brote bestellt.
Sie haben 3 Brote bestellt.
Wäre countBreads eine double-Variable, würde obiger Code leider auch Ausgaben wie
Hi nweis
»Sie haben 0.5 Brote bestellt.« oder »Sie haben 1.5 Brot bestellt.« erzeugen. Um dies zu
korrigieren, könnten Sie die Grenzen als {0, 0.5, 1} festlegen, auf {"Brote", "Brot",
"Brote"} abbilden und in der Ausgabe x.5 als "x 1/2" schreiben, also beispielsweise:
»Sie haben 1 1/2 Brote bestellt.«.
Parameter in Ausgabestrings
Leider ist es nicht möglich, das Argument der format()-Methode in den zurückgelieferten
String einzubauen. Dann bräuchte man nämlich statt
countBreads + " " + cf.format(countBreads)
46 >> Umrechnung zwischen Zahlensystemen
Zahlen
nur noch
cf.format(countBreads)
zu schreiben, und was wichtiger wäre: Man könnte in den Ausgabestrings festlegen, ob für
einen Bereich der Zahlenwert ausgegeben soll. Beispielsweise ließe sich dann das unschöne
»Sie haben 0 Brote bestellt.« durch »Sie haben kein Brot bestellt.« ersetzen.
Die Lösung bringt in diesem Fall die Klasse java.text.MessageFormat:
// ChoiceFormat-Objekt erzeugen, das Zahlenwerte Strings zuordnet
double[] limits = {0, 1, 2};
String[] outputs = {"kein Brot", "ein Brot", "{0} Brote"};
ChoiceFormat cf = new ChoiceFormat(limits, outputs);
// Ausgabe
Object[] arguments = {new Integer(number)};
System.console().printf("%s \n", mf.format(arguments)); // 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. (Beachten Sie, dass ChoiceFormat den Platzhalter nicht ersetzt, sondern
unverändert zurückliefert. Dies ist aber genau das, was wir wollen, denn das im nächsten
Schritt erzeugte MessageFormat-Objekt, das intern unser ChoiceFormat-Objekt verwendet, sorgt
für die Ersetzung des Platzhalters.)
Dann erzeugen Sie das MessageFormat-Objekt mit dem Ausgabetext. In diesen Text fügen Sie
einen Platzhalter für den Zahlenwert ein. Da der Zahlenwert von dem soeben erzeugten
ChoiceFormat-Objekt verarbeitet werden soll, registrieren Sie Letzteres mit Hilfe der setFormat-
ByArgument()-Methode als Formatierer für den Platzhalter.
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.
Zahlen
Abbildung 5: Mit ChoiceFormat können Sie (unter anderem) Mengen korrekt in
Ein- oder Mehrzahl angeben.
Einlesen Ausgeben
Zehnersystem Andere Systeme Zehnersystem Andere Systeme
Byte.parseByte Integer.parseInt Byte.toString() Integer.toString(int i, int
(String s) (String s, int Byte.toString(byte n) radix)
Short.parseShort radix) Short.toString() Integer.toBinaryString(int i)
(String s) Short.toString(short n) Integer.toOctalString(int i)
Integer.parseInt Integer.toString() Integer.toHexString(int i)
(String s) Integer.toString(int n)
Long.parseLong Long.toString() System.out.printf()
(String s)
Long.toString(long n)
Mit Hilfe dieser Methoden lässt sich auch leicht ein Hilfsprogramm schreiben, mit dem man
Zahlen zwischen den in der Programmierung am weitesten verbreiteten Zahlensystemen (2, 8,
10 und 16) umrechnen kann:
System.out.println();
if (args.length != 3) {
System.out.println(" Aufruf: Start <Ganzzahl> "
+ "<Orgin. Basis: 2, 8, 10, 16> "
+ "<Zielbasis: 2, 8, 10, 16>");
System.exit(0);
}
try {
int number = 0;
int srcRadix = Integer.parseInt(args[1]);
int tarRadix = Integer.parseInt(args[2]);
}
}
Zahlen
13 Zahlen aus Strings extrahieren
Manchmal sind die Zahlen, die man verarbeiten möchte, in Strings eingebettet. Beispielsweise
könnte die Kundennummer eines Unternehmens aus einem Buchstabencode, einem Zahlen-
code, 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.
Gleiches gilt, wenn Sie Zahlen aus einem größeren Text, beispielsweise einer Datei, extrahie-
ren müssen. Eine Möglichkeit, dies zu bewerkstelligen, wäre das Einlesen des Textes mit einem
Scanner-Objekt. Dies geht allerdings nur, wenn der Text so in Tokens zerlegt werden kann,
dass die Zahlen als Tokens verfügbar sind. Außerdem ist diese Lösung, obwohl im Einzelfall
sicher gangbar und auch sinnvoll, per se doch recht unflexibel.
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 Aus-
drücke und Pattern Matching. Zur einfacheren Verwendung definieren wir gleich zwei Metho-
den:
ArrayList<String> getPatternsInString(String s, String p)
ArrayList<String> getNumbersInString(String s)
Die Methode getPatternsInString() übernimmt als erstes Argument den String, der durch-
sucht werden soll, und als zweites Argument den regulären Ausdruck (gegeben als String), mit
dem der erste String durchsucht werden soll. Alle gefundenen Vorkommen von Textpassagen,
die durch den regulären Ausdruck beschrieben werden, werden in einer ArrayList<String>-
Collection zurückgeliefert.
Für das Pattern Matching sind in dem Paket java.util.regex die Klassen Pattern und Matcher
definiert. 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.
Als Nächstes wird ein Matcher benötigt, der den String s unter Verwendung des in pat gespei-
cherten regulären Ausdrucks durchsucht. Dieses Matcher-Objekt liefert die Pattern-Methode
matcher(), der als einziges Argument der zu durchsuchende String übergeben wird. Der Mat-
cher enthält nun alle benötigten Informationen (das Pattern und den zu durchsuchenden
String); die Methoden zum Finden der übereinstimmenden Vorkommen stellt die Klasse Mat-
cher selbst zur Verfügung:
왘 Mit boolean matches() kann man prüfen, ob der gesamte String als ein »Match« für den
regulären Ausdruck angesehen werden kann.
왘 Mit boolean find() kann man den String nach übereinstimmenden Vorkommen (»Mat-
ches«) durchsuchen. Der erste Aufruf sucht ab dem Stringanfang, nachfolgende Aufrufe
setzen die Suche hinter dem letzten gefundenen Vorkommen fort.
왘 Mit int start() und int end() bzw. String group() können Sie sich Anfangs- und End-
position bzw. das komplette zuletzt gefundene Vorkommen zurückliefern lassen.
Da die Methode getPatternsInString() einen String nach allen Vorkommen des übergebenen
Musters durchsuchen soll, ruft sie find() in einer while-Schleife auf und speichert die gefun-
denen Übereinstimmungen in einem ArrayList-Container.
50 >> Zahlen aus Strings extrahieren
Zahlen
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.ArrayList;
/**
* Pattern in einem Text finden und als ArrayList zurückliefern
*/
public static ArrayList<String> getPatternsInString(String s, String p) {
ArrayList<String> matches = new ArrayList<String>(10);
while(m.find())
matches.add( m.group() );
return matches;
}
Die zweite Methode, getNumbersInString(), liefert im String gefundene Zahlen zurück. Dank
getPatternsInString() fällt die Implementierung von getNumbersInString() nicht mehr son-
derlich schwer: Die Methode ruft einfach getPatternsInString() mit einem regulären Aus-
druck auf, der Zahlen beschreibt:
/**
* Zahlen in einem Text finden und als ArrayList zurückliefern
*/
public static ArrayList<String> getNumbersInString(String s) {
return getPatternsInString(s, "[+-]?\\d+(\\,\\d+)?");
}
Der reguläre Ausdruck von getNumbersInString() setzt Zahlen aus drei Teilen zusammen:
einem optionalen Vorzeichen ([+-]?), einer mindestens einelementigen Folge von Ziffern
(\\d+) und optional einem dritten Teil, der mit Komma eingeleitet wird und mit einer mindes-
tens einelementigen Folge von Ziffern ((\\,\\d+)?) endet. Dieser reguläre Ausdruck findet
ganze Zahlen, Gleitkommazahlen mit Komma zum Abtrennen der Nachkommastellen und
ohne Tausenderzeichen, Zahlen mit und ohne Vorzeichen, aber auch Zahlenschrott (beispiels-
weise Teile englisch formatierter Zahlen, die das Komma als Tausenderzeichen verwenden).
Wenn Sie Zahlenschrott ausschließen, gezielt nach englischen Gleitkommazahlen, beliebig
formatierten Zahlen, Hexadezimalzahlen oder irgendwelchen sonstigen Textmustern suchen
wollen, können Sie nach dem Muster von getNumbersInString() eine eigene Methode definie-
ren oder getPatternsInString() aufrufen und den passenden regulären Ausdruck als Argu-
ment übergeben.
>> Zahlen und Mathematik 51
Zahlen
System.out.println("\n Suche nach (deutschen) Zahlen: \n");
14 Zufallszahlen erzeugen
Zur Erzeugung von Zufallszahlen gibt es in der Java-Klassenbibliothek den »Zufallszahlenge-
nerator« java.util.Random. Die Arbeit mit dieser Klasse sieht so aus, 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.
import java.util.Random;
Die Erzeugung von Zufallszahlen mit Hilfe von Computern ist im Grunde gar nicht zu
H i n we i s
realisieren. Dies liegt daran, dass die Zahlen letzten Endes nicht zufällig gezogen wer-
den, sondern von einem mathematischen Algorithmus errechnet werden. Ein solcher
Algorithmus bildet eine Zahlenfolge, die sich zwangsweise irgendwann wiederholt.
Allerdings sind die Algorithmen, die man zur Erzeugung von Zufallszahlen in Pro-
grammen verwendet, so leistungsfähig, dass man von der Periodizität der erzeugten
Zahlenfolge nichts merkt.
52 >> Zufallszahlen erzeugen
Zahlen
Gaußverteilte Zufallszahlen
Die oben aufgeführten next-Methoden sind so implementiert, dass sie alle Zahlen aus ihrem
Wertebereich mit gleicher Wahrscheinlichkeit zurückliefern. In Natur und Technik hat man es
dagegen häufig mit Größen zu tun, die normalverteilt sind.
Normalverteilung
E x k ur s
Normalverteilte Größen zeichnen sich dadurch aus, dass die möglichen Werte um einen
mittleren Erwartungswert streuen. Der Erwartungswert ist am häufigsten vertreten, die
Wahrscheinlichkeit für andere Werte nimmt kontinuierlich ab, je mehr die Werte vom
Erwartungswert abweichen.
Häufigkeit
s s (Standardabweichung)
x (Mittelwert)
Wert
Eine Firma, die Schrauben herstellt, könnte beispielsweise Software für eine Messanlage in
Auftrag geben, die sicherstellen soll, dass die Durchmesser der produzierten Schrauben nor-
malverteilt sind und die Standardabweichung unterhalb eines vorgegebenen Qualitätslimits
liegt. Die zum Testen einer solchen Software benötigten normalverteilten Zufallszahlen liefert
die Random-Methode nextGaussian():
Random generator = new Random();
double d = generator.nextGaussian();
Der Erwartungswert der zurückgelieferten Zufallszahlen ist 0, die Standardabweichung 1.
Durch nachträgliche Skalierung und Addition eines Offsets können beliebige normalverteilte
Zahlen erzeugt werden.
Zahlen
natürlich sehr schwierig. (Gleiches gilt, wenn Sie Zufallszahlen als Eingaben zum Testen der
Anwendung benutzen.)
Aus diesem Grunde lassen sich Zufallsgeneratoren in der Regel so einstellen, dass sie auch
reproduzierbare Folgen von Zufallszahlen erzeugen können. Zur Einstellung dient der so
genannte Seed. Jeder Seed erzeugt genau eine vordefinierte Folge von Zufallszahlen. Wenn
Sie also dem Random-Konstruktor einen festen Seed-Wert vorgeben:
Random generator = new Random(3);
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.
Abschnitte), wählt er den Seed unter Berücksichtigung der aktuellen Zeit. So wird
gewährleistet, dass bei jedem Aufruf ein individueller Seed und damit eine individuelle
Zahlenfolge erzeugt wird.
import java.util.Random;
/**
* Zufallszahl aus vorgegebenem Bereich zurückliefern
*/
public static int randomInt(int min, int max) {
if (randomNumberGenerator == null)
initRNG();
import java.util.Random;
import java.util.TreeSet;
/**
* Erzeugt size nicht gleiche Zufallszahlen aus Wertebereich von
* min bis max
*/
public static TreeSet<Integer> uniqueRandoms(int size, int min, int max) {
TreeSet<Integer> numbers = new TreeSet<Integer>();
Random generator = new Random();
int n;
Zahlen
if (size > max+1-min)
throw new IllegalArgumentException("Gibt nicht genügend " +
"eindeutige Zahlen im Bereich!");
if (size == max+1-min) {
for(int i= min; i <= max; ++i)
numbers.add(i);
} else {
while(numbers.size() != size) {
n = min + generator.nextInt(max+1 - min);
return numbers;
}
Wenn es in dem spezifizierten Wertebereich nicht genügend Zahlen gibt, um den Container
ohne Dubletten zu füllen, wird eine IllegalArgumentException ausgeworfen.
Wenn der spezifizierte Wertebereich gerade genau so viele Zahlen enthält, wie Zahlen in den
Container eingefügt werden sollen, werden die Zahlen mit Hilfe einer for-Schleife in den Con-
tainer eingefügt. (In diesem Fall kann eigentlich nicht mehr von Zufallszahlen die Rede sein.)
Ist der Wertebereich größer als die gewünschte Anzahl Zufallszahlen, werden die Zahlen
zufällig gezogen, bis der Container die gewünschte Anzahl Elemente enthält. Beachten Sie,
dass wir uns hier nicht die Mühe machen, den Rückgabewert der add()-Methode zu überprü-
fen (true oder false), da die Bedingung der while-Schleife bereits sicherstellt, dass die Zie-
hung nicht vorzeitig beendet wird.
Das folgende Programm zeigt, wie mit Hilfe von uniqueRandoms() die Ziehung der Lottozahlen
(6 aus 49) simuliert werden kann.
import java.util.TreeSet;
Wenn Sie selbst Lotto spielen, bauen Sie das Programm und die Methode uniqueRan-
Tipp
17 Trigonometrische Funktionen
Bei Verwendung der trigonometrischen Methoden ist zu beachten, dass diese Methoden als
Parameter stets Werte in Bogenmaß (Radiant) erwarten. Beim Bogenmaß wird der Winkel
nicht in Grad, sondern als Länge des Bogens angegeben, den der Winkel aus dem Einheitskreis
(Gesamtumfang 2 π) ausschneidet: 1 rad = 360º/2 π; 1º = 2 π/360 rad.
360 Grad entsprechen also genau 2 π, 180 Grad entsprechen 1 π, 90 Grad entsprechen 1/2 π.
Wenn Sie ausrechnen wollen, was 32 Grad in Radiant sind, multiplizieren Sie einfach die
Winkelangabe mit 2 * π und teilen Sie das Ganze durch 360 (oder multiplizieren Sie mit π und
teilen Sie durch 180).
bogenlaenge = Math.PI/180 * grad;
Math stellt zur bequemen Umrechnung von Grad in Radiant und umgekehrt die Methoden
toDegrees() und toRadians() zur Verfügung. Beachten Sie aber, dass diese Umrechnung nicht
immer exakt ist. Gehen Sie also beispielsweise nicht davon aus, dass sin(toRadians(180.0))
exakt 0.0 ergibt. (Siehe auch Rezept 6 zum Vergleichen mit definierter Genauigkeit.)
/**
* Umrechnung von Celsius in Fahrenheit
*/
>> Zahlen und Mathematik 57
Zahlen
public static double celsius2fahrenheit(double temp) {
return (temp * 9 / 5.0) + 32;
}
Die Mathematik unterscheidet nicht zwischen 5/9 und 5.0/9.0 – wohl aber der Compi-
A cht un g
ler, der im ersten Fall eine Ganzzahlendivision durchführt, d.h. den Nachkommaanteil
unterschlägt.
Mit dem Programm Start.java zu diesem Rezept können Sie beliebige Temperaturwerte
umrechnen. Geben Sie einfach in der Kommandozeile die Ausgangseinheit (-f für Fahrenheit
oder -c für Celsius) und den umzurechnenden Temperaturwert an.
19 Fakultät berechnen
Mathematisch ist die Fakultät definiert als:
n! = 1, wenn n = 0
fac(n) = n * fac(n-1);
Die Fakultät ist vor allem für die Berechnung von Wahrscheinlichkeiten wichtig. Wenn Sie
beispielsweise sieben Kugeln, nummeriert von 1 bis 7, in einen Behälter geben und dann
nacheinander ziehen, gibt es 7! Möglichkeiten (Permutationen), die Kugeln zu ziehen.
/**
* Fakultät berechnen
*/
public static double factorial(int n) {
double fac = 1;
if (n < 0)
throw new IllegalArgumentException("Fakultaet ist nur fuer "
+ "positive Zahlen definiert");
if (n < 2)
return fac;
while(n > 1) {
fac *= n;
--n;
}
return fac;
}
Die Fakultät ist eine extrem schnell ansteigende Funktion. Bereits für relativ kleine
A c h tun g.
Eingaben wie die Zahl 10 ergibt sich ein sehr hoher Wert (10! = 3.628.800) und 171!
liegt schon außerhalb des Wertebereichs von double!
Mit dem Start-Programm zu diesem Rezept können Sie sich die Fakultäten von 0 bis n ausge-
ben lassen. Übergeben Sie n beim Aufruf in der Konsole und denken Sie daran, dass Sie ab 171
nur noch infinity-Ausgaben ernten.
Abbildung 9: Fakultät
>> Zahlen und Mathematik 59
Zahlen
20 Mittelwert berechnen
Wenn wir den Mittelwert oder Durchschnitt einer Folge von Zahlen berechnen, bilden wir
üblicherweise die Summe der einzelnen Werte und dividieren diese durch die Anzahl der
Werte. In der Mathematik bezeichnet man dies als das arithmetische Mittel und stellt es weite-
ren Mittelwerten gegenüber.
Mittelwert Berechnung
arithmetischer x1 + x2 + ... + xn
x=
n
geometrischer
x = n x1 ⋅ x2 ⋅ ... ⋅ xn
harmonischer n
x=
1 1 1
+ + ... +
x1 x2 xn
quadratischer
x=
1 2
n
(
x1 + x22 + ... + xn2 )
Tabelle 8: Mittelwerte
Die folgenden Methoden zur Berechnung der verschiedenen Mittelwerte wurden durchweg mit
einem double...-Parameter definiert. Als Argument kann den Methoden daher ein double-
Array oder eine beliebig lange Folge von double-Werten übergeben werden.
/**
* Arithmetisches Mittel (Standard für Mittelwertberechnungen)
*/
public static double arithMean(double... values) {
double sum = 0;
return sum/values.length;
}
/**
* Geometrisches Mittel
*/
public static double geomMean(double... values) {
double sum = 1;
/**
* Harmonisches Mittel
*/
public static double harmonMean(double... values) {
double sum = 0;
/**
* Quadratisches Mittel
*/
public static double squareMean(double... values) {
double sum = 0;
return Math.sqrt(sum/values.length);
}
Mögliche Aufrufe wären:
double[] values = {1, 5, 12.5, 0.5, 3};
MoreMath.arithMean(values);
oder
MoreMath.geomMean(1, 5, 12.5, 0.5, 3)
Die Methode geomMean() liefert NaN zurück, wenn die Summe der Werte negativ ist
Achtung
21 Zinseszins berechnen
Die Grundformel zur Zinseszinsrechnung lautet:
K n = K 0 ⋅ (1 + i )
n
wobei n die Laufzeit in Jahren und i den Jahreszinssatz (p/100) bezeichnet. Kn ist das Endka-
pital, das man erhält, wenn man das Startkapital K0 für n Jahre (oder allgemein Zinsperioden)
zu einem Zinssatz i verzinsen lässt.
Kommen monatliche Raten dazu, erweitert sich die Formel zu:
K n = K 0 ⋅ (1 + i ) + R ⋅
n (1 + i )n − 1
(1 + i )1/12 − 1
>> Zahlen und Mathematik 61
Zahlen
In der Finanzwelt wird aber meist mit der folgenden Variante für vorschüssige Renten gerech-
net:
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. Als Argumente über-
nimmt die Methode das Startkapital, das 0 sein kann, die Höhe der Raten (installment), den
Zins in Prozent (interest), der nicht 0 sein darf, und die Laufzeit (term).
/**
* Kapitalentwicklung bei monatlicher Ratenzahlung und Zinseszins
*/
public static double capitalWithCompoundInterest(double startCapital,
double installment,
double interest,
int term) {
if(interest == 0.0)
throw new IllegalArgumentException("Zins darf nicht Null sein");
return endCapital;
}
Die Höhe der reinen Einzahlungen berechnet paidInCapital():
/**
* Berechnung des eingezahlten Kapitals
*/
public static double paidInCapital(double startCapital,
double installment,
int term) {
double endCapital = startCapital;
return endCapital ;
}
Das Start-Programm zu diesem Rezept nutzt obige Methoden zur Implementierung eines Zins-
rechners. Startkapital, monatliche Raten, Verzinsung in Prozent und Laufzeit in Jahren werden
über JTextField-Komponenten abgefragt. Nach Drücken des BERECHNEN-Schalters wird die
jährliche Kapitalentwicklung berechnet und in der JTextArea-Komponente links angezeigt.
62 >> Komplexe Zahlen
Zahlen
22 Komplexe Zahlen
Komplexe Zahlen gehören zwar nicht unbedingt zum täglichen Handwerkszeug eines Pro-
grammierers, bilden aber ein wichtiges Teilgebiet der Algebra und finden als solches immer
wieder Eingang in die Programmierung, so zum Beispiel bei der Berechnung von Fraktalen.
Komplexe Zahlen haben die Form
z = x + iy
wobei x als Realteil, y als Imaginärteil und i als die imaginäre Einheit bezeichnet wird (mit
i2 = -1). Vereinfacht werden Zahlen oft als Paare aus Real- und Imaginärteil geschrieben:
(x, y).
imaginäre Achse
4 + 2i
1 reelle Achse
Zahlen
Rechnen mit komplexen Zahlen
Operation Beschreibung
Betrag Der Betrag einer komplexen Zahl ist die Quadratwurzel aus der
Summe der Komponentenquadrate.
z = x2 + y2
Addition Komplexe Zahlen werden addiert, indem man die Realteile und Ima-
ginärteile addiert.
(x, y) + (x', y') = (x + x', y + y')
Subtraktion Komplexe Zahlen werden subtrahiert, indem man die Realteile und
Imaginärteile voneinander subtrahiert.
(x, y) – (x', y') = (x – x', y – y')
Die Subtraktion entspricht der Addition der Negativen (-z = -x -yi)
Vervielfachung Vervielfachung ist die Multiplikation mit einer reellen Zahl.
3 * (x, y) = (3*x. 3*y)
Multiplikation Die Multiplikation zweier komplexer Zahlen ist gegeben durch:
(x, y) * (x', y') = (xx'-yy', xy' + yx')
Division Die Division z/z' ist gleich der Multiplikation mit der Inversen z*z-1.
Die Inverse einer komplexen Zahl ist definiert als:
x y
z −1 = 2 − i
x + y2 x2 + y2
Methode Beschreibung
Complex() Konstruktoren.
Complex(double real, double imag) Der Standardkonstruktor erzeugt eine komplexe Zahl,
Complex(double r, double phi, byte polar) deren Real- und Imaginärteil 0.0 ist.
Der zweite Konstruktor erzeugt eine komplexe Zahl mit
den übergebenen Werten für Real- und Imaginärteil.
Der dritte Konstruktor rechnet die übergebenen Werte
für Radius und Winkel in Real- und Imaginärteil um
und erzeugt das zugehörige Complex-Objekt. Um diesen
Konstruktor von dem zweiten Konstruktor unterschei-
den zu können, ist ein drittes Argument notwendig,
dem Sie einfach die Konstante Complex.POLAR überge-
ben.
double getReal() Liefert den Realteil der aktuellen komplexen Zahl
zurück.
void setReal(double real) Weist dem Realteil der aktuellen komplexen Zahl einen
Wert zu.
double getImag() Liefert den Imaginärteil der aktuellen komplexen Zahl
zurück.
void setImag(double real) Weist dem Imaginärteil der aktuellen komplexen Zahl
einen Wert zu.
double getR () Liefert den Radius (Betrag) der aktuellen komplexen
Zahl zurück. (Polarkoodinatendarstellung)
double getPhi () Liefert den Winkel (Argument) der aktuellen komple-
xen Zahl zurück. (Polarkoodinatendarstellung)
void add(Complex a) Addiert die übergebene komplexe Zahl zur aktuellen
public static Complex add(Complex a, komplexen Zahl.
Complex b) Die statische Version addiert die beiden übergebenen
komplexen Zahlen und liefert das Ergebnis zurück.
void add(double s) Addiert die übergebene reelle Zahl zur aktuellen kom-
public static Complex add(Complex a, plexen Zahl.
double s) Die statische Version addiert die reelle Zahl s zur über-
gebenen komplexen Zahl a und liefert das Ergebnis
zurück.
void subtract(Complex a) Subtrahiert die übergebene komplexe Zahl von der
public static Complex subtract(Complex a, aktuellen komplexen Zahl.
Complex b) Die statische Version subtrahiert die zweite übergebene
komplexe Zahl von der ersten und liefert das Ergebnis
zurück.
void subtract(double s) Subtrahiert die übergebene reelle Zahl von der aktuel-
public static Complex subtract(Complex a, len komplexen Zahl.
double s) Die statische Version subtrahiert die reelle Zahl s von
der übergebenen komplexen Zahl a und liefert das
Ergebnis zurück.
Zahlen
Methode Beschreibung
void times(double s) Multipliziert die aktuelle komplexe Zahl mit der über-
public static Complex times(Complex a, gebenen reellen Zahl s.
double s) Die statische Version multipliziert die übergebene kom-
plexe Zahl a mit der reellen Zahl s und liefert das
Ergebnis zurück.
void multiply(Complex a) Multipliziert die aktuelle komplexe Zahl mit der über-
public static Complex multiply(Complex a, gebenen komplexen Zahl.
Complex b) Die statische Version multipliziert die beiden übergebe-
nen komplexen Zahlen und liefert das Ergebnis zurück.
void negate() Negiert die aktuelle komplexe Zahl (-x, -yi).
double abs() Liefert den Betrag der komplexen Zahl zurück.
Complex conjugate() Liefert die konjugiert komplexe Zahl (x, -y) zur
aktuellen komplexen Zahl zurück.
Complex inverse() Liefert die Inverse zur aktuellen komplexen Zahl
zurück.
Object clone() Erzeugt eine Kopie der aktuellen komplexen Zahl.
Zur Überschreibung der clone()-Methode siehe auch
Rezept 250.
boolean equals(Object obj) Liefert true zurück, wenn das übergebene Objekt vom
static boolean equals(Complex a, Complex b, Typ Complex ist und Real- und Imaginärteil die gleichen
double eps) Werte wie die aktuelle komplexe Zahl besitzen. Zur
Überschreibung der equals()-Methode siehe auch
Rezept 252.
Die statische Version erlaubt für den Vergleich die
Angabe einer Genauigkeit eps. Die Real- bzw. Imagi-
närteile der beiden komplexen Zahlen werden dann als
»gleich« angesehen, wenn ihre Differenz kleiner eps ist.
int hashCode() Liefert einen Hashcode für die aktuelle komplexe Zahl
zurück.
String toString() Liefert eine String-Darstellung der komplexen Zahl
zurück:
x + yi
/**
* Klasse für komplexe Zahlen
*/
public class Complex implements Cloneable {
public final static byte POLAR = 1;
public Complex() {
this.real = 0.0;
this.imag = 0.0;
}
public Complex(double real, double imag) {
this.real = real;
this.imag = imag;
}
public Complex(double r, double phi, byte polar) {
this.real = r * Math.cos(phi);
this.imag = r * Math.sin(phi);
}
// Addition this += a
public void add(Complex a ) {
this.real += a.real;
this.imag += a.imag;
}
// Addition c = a + b
public static Complex add(Complex a, Complex b) {
Complex c = new Complex();
c.real = a.real + b.real;
c.imag = a.imag + b.imag;
Zahlen
return c;
}
// Subtraktion this -= a
public void subtract(Complex a ) {
this.real -= a.real;
this.imag -= a.imag;
}
// Subtraktion c = a - b
public static Complex subtract(Complex a, Complex b) {
Complex c = new Complex();
c.real = a.real - b.real;
c.imag = a.imag - b.imag;
return c;
}
r = a.real * s;
i = a.imag * s;
return new Complex(r, i);
// Multiplikation this *= b
public void multiply(Complex b){
double r, i;
this.real = r;
this.imag = i;
}
// Multiplikation c = a * b
public static Complex multiply(Complex a, Complex b){
double r, i;
// Negation
public void negate() {
this.real *= -1;
this.imag *= -1;
}
// Betrag
public double abs() {
return Math.sqrt(real*real + imag*imag);
}
// Konjugierte
public Complex conjugate() {
return new Complex(this.real, -this.imag);
}
// Inverse
public Complex inverse() {
double r, i;
Zahlen
}
Das Programm aus Listing 22 demonstriert den Einsatz der Klasse Complex anhand der Berech-
nung einer Julia-Menge. Die Berechnung der Julia-Menge erfolgt der Einfachheit halber direkt
in paintComponent(), auch wenn dies gegen den Grundsatz verstößt, in Ereignisbehandlungs-
code zeitaufwendige Berechnungen durchzuführen. Die Folge ist, dass die Benutzerschnitt-
stelle für die Dauer der Julia-Mengen-Berechnung lahm gelegt wird, was uns hier aber nicht
weiter stören soll. (Korrekt wäre die Auslagerung der Berechnung in einen eigenen Thread,
siehe Kategorie »Threads«.)
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
Zahlen
g.fillRect(i, j, 1, 1);
}
}
}
}
public Start() {
setTitle("Julia-Menge");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
23 Vektoren
Vektoren finden in der Programmierung vielfache Anwendung – beispielsweise zur Repräsen-
tation von Koordinaten, für Berechnungen mit gerichteten, physikalischen Größen wie
Geschwindigkeit oder Beschleunigung und natürlich im Bereich der dreidimensionalen Com-
putergrafik. So können – um ein einfaches Beispiel zu geben – Punkte in der Ebene P(5; 12)
oder im Raum (5; 12; -1) als zwei- bzw. dreidimensionale Ortsvektoren (d.h. mit Beginn im
Ursprung des Koordinatensystems) repräsentiert werden.
⎛5⎞
⎛5⎞ ⎜ ⎟
p = ⎜⎜ ⎟⎟, bzw. p = ⎜ 12 ⎟
⎝12 ⎠ ⎜ − 1⎟
⎝ ⎠
Der Abstand zwischen zwei Punkten P und Q ist dann gleich der Länge des Vektors, der vom
einen Punkt zum anderen führt.
PQ = q − p
Operation Beschreibung
Länge Die Länge (oder der Betrag) eines Vektors ist die Quadratwurzel aus
der Summe der Komponentenquadrate. (Für zweidimensionale Vekto-
ren 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, der vom Anfang des ersten Vektors zum
Ende des zweiten Vektors weist.
Subtraktion Vektoren werden subtrahiert, indem man ihre einzelnen Komponen-
ten subtrahiert.
⎛1⎞ ⎛ 5 ⎞ ⎛ 1 − 5 ⎞ ⎛ − 4 ⎞
⎜⎜ 3 ⎟⎟ − ⎜⎜ 0 ⎟⎟ = ⎜⎜ 3 − 0 ⎟⎟ = ⎜⎜ 3 ⎟⎟
⎝ ⎠ ⎝ ⎠ ⎝ ⎠ ⎝ ⎠
Für zwei Ortsvektoren p und q erhält man den Vektor, der von P nach
Q führt, indem man p von q subtrahiert.
Vervielfachung Vervielfachung ist die Multiplikation mit einem skalaren Faktor.
⎛ 10 ⎞ ⎛ 2 *10 ⎞ ⎛ 20 ⎞
2 ⎜⎜ ⎟⎟ = ⎜⎜ ⎟⎟ = ⎜⎜ ⎟⎟
⎝ − 7 ⎠ ⎝ 2 * −7 ⎠ ⎝ − 14 ⎠
Durch die Vervielfachung wird lediglich die Länge, nicht die Richtung
des Vektors verändert. Einer »Division« entspricht die Multiplikation
mit einem Faktor zwischen 0 und 1.
Zahlen
Operation Beschreibung
Skalarprodukt 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.
v ⋅ w = v w cos α
Für zwei- 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.
Stehen die beiden Vektoren senkrecht zueinander, ist das Skalar-
produkt gleich null.
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 sin α
⎛ α 1 ⎞ ⎛ β1 ⎞ ⎛ α 2 β 3 − α 3 β 2 ⎞
⎜ ⎟ ⎜ ⎟ ⎜ ⎟
⎜ α 2 ⎟ × ⎜ β 2 ⎟ = ⎜ α 3 β1 − α 1 β 3 ⎟
⎜α ⎟ ⎜ β ⎟ ⎜ α β − α β ⎟
⎝ 3⎠ ⎝ 3⎠ ⎝ 1 2 2 1 ⎠
Das Vektorprodukt zweier Vektoren v und w ist ein Vektor, der senk-
recht zu v und w steht. Die drei Vektoren bilden ein Rechtssystem
(Drei-Finger-Regel). Der Betrag des Vektorprodukts ist gleich dem
Flächeninhalt des von v und w aufgespannten Parallelogramms.
In der 3D-Grafikprogrammierung kann das Vektorprodukt zur
Berechnung von Oberflächennormalen verwendet werden.
Methode Beschreibung
Vector3D() Konstruktoren.
Vector3D(double x, double y, double z) Der Standardkonstruktor erzeugt einen Vektor,
dessen x,y,z-Felder auf 0.0 gesetzt sind.
Der zweite Konstruktor weist den Feldern die
übergebenen Werte zu.
void add(Vector3D v) Addiert den übergebenen Vektor zum aktuellen
static Vector3D add(Vector3D v1, Vector3D v2) Vektor.
Die statische Version addiert die beiden über-
gebenen Vektoren und liefert das Ergebnis als
neuen Vektor zurück.
double angle(Vector3D v) Berechnet den Winkel zwischen dem aktuellen
und dem übergebenen Vektor.
Der Winkel wird in Bogenmaß zurückgeliefert
(und kann beispielsweise mit Math.toDegrees()
in Grad umgerechnet werden).
Object clone() Erzeugt eine Kopie des aktuellen Vektors.
Zur Überschreibung der clone()-Methode siehe
auch Rezept 250.
Vector3D crossProduct(Vector3D v) Berechnet das Vektorprodukt aus dem aktuel-
len und dem übergebenen Vektor.
double dotProduct(Vector3D v) Berechnet das Skalarprodukt aus dem aktuel-
len und dem übergebenen Vektor.
boolean equals(Object obj) Liefert true zurück, wenn das übergebene
Objekt vom Typ Vector3D ist und die Felder x, y
und z die gleichen Werte wie im aktuellen Vek-
tor haben.
Zur Überschreibung der equals()-Methode
siehe auch Rezept 252.
double length() Berechnet die Länge des Vektors.
void scale(double s) Skaliert den aktuellen Vektor um den Faktor s.
static Vector3D scale(Vector3D v, double s) Skaliert den übergebenen Vektor um den Fak-
tor s und liefert das Ergebnis als neuen Vektor
zurück.
void subtract(Vector3D v) Subtrahiert den übergebenen Vektor vom aktu-
static Vector3D subtract(Vector3D v1, Vector3D v2) ellen Vektor.
Die statische Version subtrahiert den zweiten
vom ersten Vektor und liefert das Ergebnis als
neuen Vektor zurück.
String toString() Liefert eine String-Darstellung des Vektors
zurück:
(x; y; z)
Zahlen
/**
* Klasse für dreidimensionale Vektoren
*
*/
public class Vector3D implements Cloneable {
public double x;
public double y;
public double z;
// Konstruktoren
public Vector3D() {
x = 0;
y = 0;
z = 0;
}
public Vector3D(double x, double y, double z) {
this.x = x;
this.y = y;
this.z = z;
}
// Addition
public void add(Vector3D v) {
x += v.x;
y += v.y;
z += v.z;
}
public static Vector3D add(Vector3D v1, Vector3D v2) {
return new Vector3D(v1.x+v2.x, v1.y+v2.y, v1.z+v2.z);
}
// Subtraktion
public void subtract(Vector3D v) {
x -= v.x;
y -= v.y;
z -= v.z;
}
public static Vector3D subtract(Vector3D v1, Vector3D v2) {
return new Vector3D(v1.x-v2.x, v1.y-v2.y, v1.z-v2.z);
}
// Skalarprodukt
public double dotProduct(Vector3D v) {
return x*v.x + y*v.y + z*v.z;
}
// Vektorprodukt
public Vector3D crossProduct(Vector3D v) {
return new Vector3D(y*v.z - z*v.y,
z*v.x - x*v.z,
x*v.y - y*v.x);
}
// Länge
public double length() {
return Math.sqrt(x*x + y*y + z*z);
}
// Umwandlung in String
public String toString() {
return "(" + x + "; " + y + "; " + z + ")";
}
// Kopieren
public Object clone() {
try {
Vector3D v = (Vector3D) super.clone();
v.x = x;
v.y = y;
v.z = z;
return v;
} catch (CloneNotSupportedException e) {
// sollte nicht vorkommen
throw new InternalError();
}
}
// Vergleichen
public boolean equals(Object obj) {
if (obj instanceof Vector3D) {
if ( x == ((Vector3D) obj).x
&& y == ((Vector3D) obj).y
&& z == ((Vector3D) obj).z)
return true;
}
Zahlen
return false;
}
}
Das Programm aus Listing Listing 24: demonstriert den Einsatz der Klasse Vector3D anhand
eines geometrischen Problems. Mittels Vektoren wird ausgehend von den Punktkoordinaten
eines Dreiecks der Flächeninhalt berechnet.
// Punktvektoren
Vector3D a = new Vector3D(2, 3, 0);
Vector3D b = new Vector3D(2.5, 5, 0);
Vector3D c = new Vector3D(7, 4, 0);
// Kantenvektoren
Vector3D ab = Vector3D.subtract(b, a);
Vector3D ac = Vector3D.subtract(c, a);
24 Matrizen
In der Mathematik ist eine Matrix ein rechteckiges Zahlenschema. Als (m,n)-Matrix oder
Matrix der Ordnung m × n bezeichnet man eine Anordnung aus m Zeilen und n Spalten:
⎛α α 12 α 13 ⎞
A = ⎜⎜ 11 ⎟ (Beispiel für eine (2, 3)-Matrix)
α
⎝ 21 α 22 α 23 ⎟⎠
Matrizen können lineare Abbildungen repräsentieren (eine (m,n)-Matrix entspricht einer line-
aren Abbildung vom Vektorraum Vn nach Vm) oder auch lineare Gleichungssysteme. In der
Programmierung werden Matrizen vor allem zur Lösung linearer Gleichungssysteme sowie für
Vektortransformationen in 3D-Grafikanwendungen eingesetzt.
Operation Beschreibung
Addition Matrizen werden addiert, indem man ihre einzelnen Komponenten
addiert.
⎛ α 11 α12 ⎞ ⎛ β11 β12 ⎞ ⎛ α11 + β11 α 12 + β12 ⎞
⎜⎜ ⎟⎟ + ⎜⎜ ⎟=⎜ ⎟
⎝ α 21 α 22 ⎠ ⎝ β 21 β 22 ⎟⎠ ⎜⎝ α 21 + β 21 α 22 + β 22 ⎟⎠
Zwei Matrizen, die addiert werden, müssen der gleichen Ordnung
angehören.
Subtraktion Matrizen werden subtrahiert, indem man ihre einzelnen Komponen-
ten subtrahiert.
⎛ α 11 α 12 ⎞ ⎛ β11 β12 ⎞ ⎛ α 11 − β11 α 12 − β12 ⎞
⎜⎜ ⎟⎟ − ⎜⎜ ⎟=⎜ ⎟
⎝ α 21 α 22 ⎠ ⎝ β 21 β 22 ⎟⎠ ⎜⎝ α 21 − β 21 α 22 − β 22 ⎟⎠
Zwei Matrizen, die subtrahiert werden, müssen der gleichen Ordnung
angehören.
Vervielfachung Vervielfachung ist die Multiplikation mit einem skalaren Faktor.
⎛α α 12 ⎞ ⎛ kα11 kα 12 ⎞
k ⎜⎜ 11 ⎟⎟ = ⎜⎜ ⎟
⎝ 21 α 22 ⎠ ⎝ kα 21
α kα 22 ⎟⎠
Zahlen
Operation Beschreibung
Multiplikation Bei der Matrizenmultiplikation C = A*B ergeben sich die Elemente der
Ergebnismatrix C durch Aufsummierung der Produkte aus den Ele-
menten einer Zeile von A mit den Elementen einer Spalte von B:
Spalten von A
cij = ∑a
k =1
ik kjb
Eine Multiplikation ist nur möglich, wenn die Anzahl von Spalten
von A gleich der Anzahl Zeilen von B ist. Das Ergebnis aus der Multi-
plikation einer (m,n)-Matrix A mit einer (n,r)-Matrix B ist eine (m,r)-
Matrix.
⎛ α 11 α 12 ⎞ ⎛ β11 ⎞ ⎛ (α 11 * β11 ) + (α 12 * β 21 ) ⎞
⎜⎜ ⎟⎟ * ⎜⎜ ⎟⎟ = ⎜⎜ ⎟⎟
⎝ α 21 α 22 ⎠ ⎝ β 21 ⎠ ⎝ (α 21 * β11 ) + (α 22 * β 21 ) ⎠
Methode Beschreibung
Matrix(int m, int n) Konstruktoren.
Matrix(int m, int n, double s) Erzeugt wird jeweils eine (m,n)-Matrix (n Zeilen, m
Matrix(int m, int n, double[][] elems) Spalten). Die Elemente der Matrix werden je nach Kon-
struktor mit 0.0, mit s oder mit den Werten aus dem
zweidimensionalen Array elems initialisiert.
Negative Zeilen- oder Spaltendimensionen führen zur
Auslösung einer NegativeArraySizeException.
Wird zur Initialisierung ein Array übergeben, müssen
dessen Dimensionen mit m und n übereinstimmen.
Ansonsten wird eine IllegalArgumentException ausge-
löst.
Methode Beschreibung
void add(Matrix B) Addiert die übergebene Matrix zur aktuellen Matrix.
static Matrix add(Matrix A, Matrix B) Die statische Version addiert die beiden übergebenen
Matrizen und liefert das Ergebnis als neue Matrix
zurück.
Gehören die Matrizen unterschiedlichen (n,m)-Ordnun-
gen an, wird eine IllegalArgumentException ausgelöst.
Object clone() Erzeugt eine Kopie der Matrix.
Zur Überschreibung der clone()-Methode siehe auch
Rezept 250.
boolean equals(Object obj) Liefert true zurück, wenn das übergebene Objekt vom
Typ Matrix ist und die Elemente die gleichen Werte wie
die Elemente der aktuellen Matrix haben.
Zur Überschreibung der equals()-Methode siehe auch
Rezept 252.
double det() Liefert die Determinante der aktuellen Matrix zurück.
Für nichtquadratische oder singuläre Matrizen wird eine
IllegalArgumentException ausgelöst.
double get(int i, int j) Liefert den Wert des Elements in Zeile i, Spalte j zurück.
double[][] getArray() Liefert die Elemente der Matrix als zweidimensionales
Array zurück.
int getColumnDim() Liefert die Anzahl der Spalten (n).
static Matrix getIdentity(int n, int m) Erzeugt eine Identitätsmatrix mit m Zeilen und n Spal-
ten.
In einer Identitätsmatrix haben alle Diagonalelemente
den Wert 1.0, während die restlichen Elemente gleich
null sind.
Bei der 3D-Grafikprogrammierung kann die Identität als
Ausgangspunkt zur Erzeugung von Translations- und
Skalierungsmatrizen verwendet werden.
int getRowDim() Liefert die Anzahl der Zeilen (m).
Matrix inverse() Liefert die Inverse der aktuellen Matrix zurück. Existiert
die Inverse, gilt
A*A-1 = I
Wenn die aktuelle Matrix nicht quadratisch oder singu-
lär ist, wird eine IllegalArgumentException ausgelöst.
Zahlen
Methode Beschreibung
LUMatrix luDecomp() Liefert die LR-Zerlegung der aktuellen Matrix als Objekt
der Hilfsklasse LUMatrix zurück. Die Zerlegung, die der
Konstruktor von LUMatrix vornimmt, erfolgt nach dem
Verfahren von Crout.
Die LUMatrix hat den folgenden Aufbau:
Methode Beschreibung
void times(double s) Multipliziert die Elemente der aktuellen bzw. der über-
static Matrix times(Matrix A, double s) gebenen Matrix mit dem Faktor s.
void transpose() Transponiert die Matrix bzw. liefert die Transponierte
static Matrix transpose(Matrix A) zur übergebenen Matrix zurück.
Die Transponierte ergibt sich durch Spiegelung der Ele-
mente an der Diagonalen, sprich durch paarweise Ver-
tauschung der Elemente a(ij) mit a(ji).
Wenn die zu transponierende Matrix nicht quadratisch
ist, wird eine RuntimeException bzw. IllegalArgumentEx-
ception ausgelöst.
/**
* Klasse für Matrizen
*
* @author Dirk Louis
*/
import java.text.DecimalFormat;
Zahlen
for (int i = 0; i < m; ++i) // für alle Zeilen die Anzahl Spalten
if (n != elems[i].length) // gleich m?
throw new IllegalArgumentException("Fehler in Spaltendimension");
this.m = m;
this.n = n;
this.elems = elems;
}
// Addition C = A + B
public static Matrix add(Matrix A, Matrix B) {
int m = A.getRowDim();
int n = A.getColumnDim();
if (m != B.getRowDim() || n != B.getColumnDim() )
throw new IllegalArgumentException("Matrix-Dim. passen nicht.");
// Subtraktion C = A - B
public static Matrix subtract(Matrix A, Matrix B) {
int m = A.getRowDim();
int n = A.getColumnDim();
if (m != B.getRowDim() || n != B.getColumnDim() )
throw new IllegalArgumentException("Matrix-Dim. passen nicht.");
// Vervielfachung C = B * s
public static Matrix times(Matrix B, double s) {
int m = B.getRowDim();
int n = B.getColumnDim();
double[][] newElems = new double[m][n];
double[][] elemsB = B.getArray();
// Multiplikation C = THIS * B
public Matrix multiply(Matrix B) {
if (this.n != B.getRowDim())
throw new IllegalArgumentException("Matrix-Dim. passen nicht.");
int m = this.getRowDim();
int n = B.getColumnDim();
double[][] newElems = new double[m][n];
double[][] elemsB = B.getArray();
double sum = 0;
Zahlen
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
sum = 0;
for (int k = 0; k < this.getColumnDim(); k++)
sum += this.elems[i][k] * elemsB[k][j];
newElems[i][j] = sum;
}
}
this.elems = transelems;
}
if (m != n)
throw new IllegalArgumentException("Matrix-Dim. passen nicht.");
// Identitätsmatrix C = mxn-I
public static Matrix getIdentity(int m, int n) {
double[][] idelems = new double[m][n];
for (int i = 0; i < m; i++)
for (int j = 0; j < n; j++)
idelems[i][j] = (i == j ? 1.0 : 0.0);
// LR-Zerlegung
public LUMatrix luDecomp() {
return new LUMatrix(this);
}
// Gleichungssystem lösen
public double[] solve(double[] bvec) {
LUMatrix LU = luDecomp();
double[] xvec = LU.luBacksolve(bvec);
return xvec;
}
LUMatrix LU = luDecomp();
col[j] = 1.0;
xvec = LU.luBacksolve(col);
// Determinante
public double det() {
LUMatrix LU = luDecomp();
double d = LU.getD();
Zahlen
return d;
}
// Get/Set-Methoden
public double[][] getArray() {
return elems;
}
// Kopieren
public Object clone() {
try {
Matrix B = (Matrix) super.clone();
B.elems = new double[m][n];
for (int i = 0; i < m; i++)
for (int j = 0; j < n; j++)
B.elems[i][j] = this.elems[i][j];
return B;
} catch (CloneNotSupportedException e) {
// sollte nicht vorkommen
throw new InternalError();
}
}
// Vergleichen
public boolean equals(Object obj) {
if (obj instanceof Matrix) {
Matrix B = (Matrix) obj;
if (B.getRowDim() == m && B.getColumnDim() == n) {
for (int i = 0; i < m; i++)
for (int j = 0; j < n; j++)
if(B.get(i, j) != this.elems[i][j])
return false;
return true;
}
return false;
}
return false;
}
Das Programm aus Listing 26 demonstriert die Programmierung mit Objekten der Klasse
Matrix anhand einer Matrixmultiplikation und der Berechnung einer Inversen.
A.print();
System.out.println("\n multipliziert mit \n");
B.print();
System.out.println("\n ergibt: \n");
Zahlen
// Matrizen multiplizieren
Matrix C = A.multiply(B);
C.print();
double[][] elemsD = { { 1, 0 },
{ 1, 2} };
Matrix D = new Matrix(2, 2, elemsD);
D.print();
// Inverse berechnen
C = D.inverse();
C.print();
System.out.println();
}
}
25 Gleichungssysteme lösen
Mit Hilfe der Methode solve() der im vorangehenden Rezept beschriebenen Klasse Matrix
können Sie lineare Gleichungssysteme lösen, die sich durch quadratische Matrizen repräsen-
tieren lassen. Intern wird dabei ein Objekt der Hilfsklasse LUMatrix erzeugt, welches die LU-
Zerlegung der aktuellen Matrix repräsentiert. Die Klasse selbst ist hier nicht abgedruckt, steht
aber – wie alle anderen zu diesem Buch gehörenden Klassen – als Quelltext auf der Buch-CD
zur Verfügung. Der Code der Klasse ist ausführlich kommentiert.
Das Programm aus Listing 26 demonstriert, wie mit Hilfe der Klasse Matrix ein lineares Glei-
chungssystem gelöst werden kann.
// Koeffizientenmatrix erzeugen
double[][] elems = { { 1, 5, -1},
{ -3, 1, -1},
{ 3, 1, 1} };
Matrix A = new Matrix(3, 3, elems);
double[] bvec = { 1, 1, -3 };
// Gleichungssystem lösen
double[] loesung = A.solve(bvec);
System.out.println();
}
}
Zahlen
Abbildung 15: Lösung eines linearen Gleichungssystems durch Zerlegung der
Koeffizientenmatrix
Ein Anfangskapital von 200 € soll für 9 Jahre bei einer vierteljährlichen Verzinsung von
0,25% p.Q. angelegt werden.
Das folgende Programm berechnet das Endkapital gemäß der Zinseszins-Formel Kn = K0
(1 + p/100)n in den drei Datentypen float, double und BigDecimal.
import java.math.BigDecimal;
/*
* Zinseszins-Berechnung mit float-Werten
*/
static float compoundInterest(float startCapital,
float interestRate, int term) {
return startCapital *
(float) Math.pow(1.0 + interestRate, term);
}
/*
* Zinseszins-Berechnung mit double-Werten
*/
static double compoundInterest(double startCapital,
double interestRate, int term) {
return startCapital * Math.pow(1.0 + interestRate, term);
}
/*
* Zinseszins-Berechnung mit BigDecimal-Werten
*/
Zahlen
static BigDecimal compoundInterest(BigDecimal startCapital,
BigDecimal interestRate, int term) {
interestRate = interestRate.add(new BigDecimal(1.0));
return startCapital.multiply(factor);
}
Ausgabe:
486.50708
486.5070631435786
486.50706314358007
Für die Ausgabe des BigDecimal-Werts ist die Umwandlung in double notwendig, da
Achtung
toString() eine Stringdarstellung des BigDecimal-Objekts liefert, aus der der tatsächli-
che Wert praktisch nicht herauszulesen ist. Den mit der Umwandlung einhergehenden
Genauigkeitsverlust müssen wir also in Kauf nehmen. Er ist aber nicht so tragisch. Ent-
scheidend ist, dass die Formel (insbesondere die Potenz!), mit erhöhter Genauigkeit
berechnet wurde.
Strings
Strings
27 In Strings suchen
Da Strings nicht sortiert sind, werden sie grundsätzlich sequentiell (von vorn nach hinten oder
umgekehrt von hinten nach vorn) durchsucht: Trotzdem lassen sich drei alternative Suchver-
fahren unterscheiden, nämlich die Suche nach
왘 einzelnen Zeichen,
왘 Teilstrings,
왘 Mustern (regulären Ausdrücken).
Grundsätzlich gilt, dass die aufgeführten Suchverfahren von oben nach unten immer leis-
tungsfähiger, aber auch immer teurer werden.
/**
* String nach Teilstrings durchsuchen
*/
public static int indexOfString(String text, String searched) {
return indexOfString(text, searched, 0);
}
if (tmp.equals(searched))
break; // Vorkommen gefunden
}
++found;
}
return found;
}
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, d.h., sie ruft ein-
fach die zweite Methode mit der Startposition 0 auf. 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. Sie geht dabei so vor, dass sie
zuerst mit indexOf() nach dem Anfangsbuchstaben von searched sucht. Wurde ein Vorkom-
men dieses Buchstabens gefunden, kopiert die Methode ab seiner Position aus text einen
String heraus, der ebenso groß ist wie der gesuchte String (sofern die Länge von text dies
zulässt), und vergleicht diesen mit searched. Stimmen beide Strings überein, wird die Suche
abgebrochen und die gefundene Position zurückgeliefert. Wird kein Vorkommen von searched
gefunden, liefert die Methode -1 zurück.
Eingesetzt wird die Methode so wie indexOf():
// Erstes Vorkommen von "John Maynard" in String text
int pos = MoreString.indexOfString(text, "John Maynard");
Strings
while((found = MoreString.indexOfString(text, "John Maynard", found)) != -1) {
// hier Vorkommen an Position end verarbeiten
...
++found;
}
Die String-Methode matches() ist nicht zum Durchsuchen von Strings geeignet. Sie
A c h t u ng
Im Start-Programm zu diesem Rezept werden die hier vorgestellten Suchverfahren alle noch
einmal eingesetzt und demonstriert.
98 >> In Strings einfügen und ersetzen
Strings
Das Start-Programm zu diesem Rezept demonstriert die Ersetzung mit der replace()-Methode
von StringBuilder und der replaceAll()-Methode von String. (Hinweis: Das Programm ist
trotz der Erwähnung eines bekannten Politikers nicht als politischer Kommentar gedacht, son-
dern spielt lediglich – in Anlehnung an den »Lotsen« Bismarck – mit der Vorstellung vom
Kanzler als Steuermann.)
>> Strings 99
Strings
public class Start {
// Originaltext ausgeben
System.console().printf("\n%s", text);
}
}
29 Strings zerlegen
Zum Zerlegen von Strings steht die String-Methode split() zur Verfügung.
String[] split(String regex)
Der split()-Methode liegt die Vorstellung zugrunde, dass der zu zerlegende Text aus mehre-
ren informationstragenden Passagen besteht, die durch spezielle Zeichen oder Zeichenfolgen
getrennt sind. Ein gutes Beispiel ist ein String, der mehrere Zahlenwerte enthält, die durch
Semikolon getrennt sind:
String data = "1;-234;5623;-90";
Die Zahlen sind in diesem Fall die eigentlich interessierenden Passagen, die extrahiert werden
sollen. Die Semikolons dienen lediglich als Trennzeichen und sollen bei der Extraktion ver-
worfen werden (d.h., sie sollen nicht mehr als Teil der zurückgelieferten Strings auftauchen).
Für solche Fälle ist split() ideal. Sie übergeben einfach das Trennzeichen (in Form eines
Strings aus einem Zeichen) und erhalten die zwischen den Trennzeichen stehenden Textpassa-
gen als String-Array zurück.
String[] buf = data.split(";");
Selbstverständlich können Sie auch Strings aus mehreren Zeichen als Trennmarkierung über-
geben. Da der übergebene String als regulärer Ausdruck interpretiert wird, können Sie sogar
durch Definition einer passenden Zeichenklasse nach mehreren alternativen Trennmarkierun-
gen suchen lassen:
String[] buf = data.split("[;/]"); // erkennt Semikolon und
// Schrägstrich als Trennmarkierung
Abbildung 17: Beispiel für die Zerlegung eines Textes in einzelne Wörter
Strings
// aus Start.java
// Text, der mit Zeilenumbrüchen und zusätzlichen Leerzeichen formatiert wurde
String text = "Sei getreu bis in den Tod,\n so will ich dir die Krone des "
+ "Lebens geben.";
30 Strings zusammenfügen
Dass Strings mit Hilfe des +-Operators oder der String-Methode concat() aneinander gehängt
werden können (Konkatenation), ist allgemein bekannt.
Zu beachten ist allerdings, dass diese Operationen, wie im Übrigen sämtliche Manipulationen
von String-Objekten, vergleichsweise kostspielig sind, da String-Objekte immutable, sprich
unveränderlich, sind. Alle String-Operationen, die den ursprünglichen String verändern wür-
den, werden daher nicht auf den Originalstrings, sondern einer Kopie ausgeführt!
Wenn Sie also – um ein konkretes Beispiel zu geben – an einen bestehenden String einen
anderen String anhängen, werden die Zeichen des zweiten Strings nicht einfach an das letzte
Zeichen des ersten Strings angefügt, sondern es wird ein ganz neues String-Objekt erzeugt, in
welches die Zeichen der beiden aneinander zu hängenden Strings kopiert werden.
Solange die entsprechenden String-Manipulationen nur gelegentlich durchgeführt werden, ist
der Overhead, der sich durch die Anfertigung der Kopie ergibt, verschmerzbar. Sobald aber auf
einen String mehrere manipulierende Operationen nacheinander ausgeführt werden, stellt sich
die Frage nach einer ressourcenschonenderen Vorgehensweise.
Java definiert zu diesem Zweck die Klassen StringBuilder und StringBuffer. Diese stellen
Methoden zur Verfügung, die direkt auf dem aktuellen Objekt (letzten Endes also der Zeichen-
kette, die dem String zugrunde liegt) operieren und mit deren Hilfe Konkatenations-, Einfüge-
und Ersetzungsoperationen effizient durchgeführt werden können.
String partOne = "Der Grund, warum wir uns über die Welt täuschen, ";
String partTwo = "liegt sehr oft darin, ";
String partThree = "dass wir uns über uns selbst täuschen.";
Wenn Sie einen String in ein StringBuilder-Objekt verwandeln wollen, um es effizienter bear-
beiten zu können, dürfen Sie nicht vergessen, die Kosten für die Umwandlung in StringBuil-
der und wieder zurück in String in Ihre Kosten-Nutzen-Analyse mit einzubeziehen.
Denken Sie aber auch daran, dass der Geschwindigkeitsvorteil von StringBuilder.append()
nicht nur darin besteht, dass kein neues String-Objekt erzeugt werden muss. Mindestens
ebenso wichtig ist, dass die Zeichen des Ausgangsstrings (gemeint ist der String, an den ange-
hängt wird) nicht mehr kopiert werden müssen. Je länger der Ausgangsstring ist, umso mehr
Zeit wird also eingespart.
Mit dem Start-Programm zu diesem Rezept können Sie messen, wie viel Zeit 10.000 String-
Konkatenationen mit dem String-Operator + und mit der StringBuilder-Methode append()
benötigen.
Die Klassen StringBuilder und StringBuffer verfügen über praktisch identische Kon-
struktoren und Methoden und erlauben direkte Operationen auf den ihnen zugrunde
liegenden Strings. Die Klasse StringBuffer wurde bereits in Java 1.2 eingeführt und ist
synchronisiert, d.h., sie eignet sich für Implementierungen, in denen mehrere Threads
gleichzeitig auf einen String zugreifen.
So vorteilhaft es ist, eine synchronisierte Klasse für String-Manipulationen zur Verfü-
gung zu haben, so ärgerlich ist es, in den 90% der Fälle, wo Strings nur innerhalb eines
Threads benutzt werden, den unnötigen Ballast der Synchronisierung mitzuschleppen.
Aus diesem Grund wurde in Java 5 StringBuilder eingeführt – als nichtsynchronisier-
tes Pendant zu StringBuffer.
Sofern Sie also nicht mit einem älteren JDK (vor Version 1.5) arbeiten oder Strings
benötigen, auf die von mehreren Threads aus zugegriffen wird, sollten Sie stets String-
Builder verwenden. Im Übrigen kann Code, der mit StringBuilder arbeitet, ohne große
Mühen nachträglich auf StringBuffer umgestellt werden: Sie müssen lediglich alle
Vorkommen von StringBuilder durch StringBuffer ersetzen. (Gilt natürlich ebenso für
die umgekehrte Richtung.)
Strings
/**
* Strings nach ersten n Zeichen vergleichen
* Rückgabewert ist kleiner, gleich oder größer Null
*/
public static int compareN(String s1, String s2, int n) {
if (n < 1)
throw new IllegalArgumentException();
Die Methode compareN() ruft für den eigentlichen Vergleich die String-Methode compareTo()
auf. Folglich übernimmt sie auch die compareTo()-Semantik: Der Rückgabewert ist kleiner,
gleich oder größer null, je nachdem, ob der erste String kleiner, gleich oder größer als der
zweite String ist. Verglichen wird nach dem Unicode und der zurückgelieferte Zahlenwert gibt
die Differenz zwischen den Unicode-Werten des ersten unterschiedlichen Zeichenpaars an
(bzw. der Stringlängen, falls die Zeichenpaare alle gleich sind).
Sollen die Strings lexikografisch verglichen werden, muss der Vergleich mit Hilfe von Collator.
compare() durchgeführt werden:
/**
* Strings nach ersten n Zeichen gemäß Lokale vergleichen
* Rückgabewert ist -1, 0 oder 1
*/
public static int compareN(String s1, String s2, int n, Locale loc) {
if (n < 1)
throw new IllegalArgumentException();
Listing 34: Strings lexikografisch nach den ersten n Zeichen vergleichen (Forts.)
In der Datei MoreString.java zu diesem Rezept sind noch zwei weitere Methoden defi-
H in we is
Das Start-Programm zu diesem Rezept liest über die Befehlszeile zwei Strings und die Anzahl
der zu vergleichenden Zeichen ein. Dann vergleicht das Programm die beiden Strings auf vier
verschiedene Weisen:
왘 gemäß Unicode, mit Berücksichtigung der Groß-/Kleinschreibung.
왘 gemäß Unicode, ohne Berücksichtigung der Groß-/Kleinschreibung.
왘 gemäß der deutschen Lokale, mit Berücksichtigung der Groß-/Kleinschreibung.
왘 gemäß der deutschen Lokale, ohne Berücksichtigung der Groß-/Kleinschreibung.
import java.util.Locale;
if (args.length != 3) {
System.out.println(" Aufruf: Start <String> <String> <Ganzzahl>");
System.exit(0);
}
try {
int n = Integer.parseInt(args[2]);
if(erg < 0) {
System.out.println(" String 1 ist kleiner");
} else if(erg == 0) {
System.out.println(" Strings sind gleich");
} else {
System.out.println(" String 1 ist groesser");
}
Strings
...
switch(erg) {
case -1: System.out.println(" String 1 ist kleiner");
break;
case 0: System.out.println(" Strings sind gleich");
break;
case 1: System.out.println(" String 1 ist groesser");
break;
}
...
}
catch (NumberFormatException e) {
System.err.println(" Ungueltiges Argument");
}
}
}
Abbildung 18: Ob ein String größer oder kleiner als ein anderer String ist, hängt vor
allem von der Vergleichsmethode ab!
Die in diesem Rezept vorgestellten Methoden sind, man muss es so hart sagen, alles
T ip p
andere als effizient implementiert: Die Rückführung auf vorhandene API-Klassen sorgt
für eine saubere Implementierung, bedeutet aber zusätzlichen Function Overhead, und
die Manipulation der tatsächlich ja unveränderbaren String-Objekte führt im Hinter-
grund zu versteckten Kopieraktionen. Wesentlich effizienter wäre es, die Strings in
char-Arrays zu verwandeln (String-Methode toCharArray()) und diese selbst Zeichen
für Zeichen zu vergleichen. Der Aufwand lohnt sich aber nur, wenn Sie wirklich exzes-
siven Gebrauch von diesen Methoden machen wollen.
106 >> Zeichen (Strings) vervielfachen
Strings
import java.util.Arrays;
/**
* Zeichen vervielfältigen
*/
public static String charNTimes(char c, int n) {
if (n > 0) {
char[] tmp = new char[n];
Arrays.fill(tmp, c);
Beachten Sie, dass die Methode nicht einfach ein leeres String-Objekt erzeugt und diesem mit
Hilfe des +-Operators n Mal das Zeichen c anhängt. Diese Vorgehensweise würde wegen der
Unveränderbarkeit von String-Objekten dazu führen, dass n+1 String-Objekte erzeugt wür-
den. Stattdessen wird ein char-Array passender Größe angelegt und mit dem Zeichen c gefüllt.
Anschließend wird das Array in einen String umgewandelt und als Ergebnis zurückgeliefert.
rung an einem String-Objekt, sei es durch den +-Operator oder eine der String-Metho-
den führt dazu, dass ein neues String-Objekt mit den gewünschten Änderungen
erzeugt wird.
Die Schwestermethode heißt strNTimes() und erzeugt einen String, der aus n Kopien des
Strings s besteht.
/**
* String vervielfältigen
*/
public static String strNTimes(String s, int n) {
StringBuilder tmp = new StringBuilder();
Strings
for(int i = 1; i <= n; ++i)
tmp.append(s);
return tmp.toString();
}
Strings, d.h., die Methoden dieser Klassen operieren auf dem aktuellen Objekt und ver-
ändern dessen Zeichenfolge (statt wie im Fall von String ein neues Objekt zu erzeu-
gen). Die Klassen StringBuilder und StringBuffer besitzen identische Methoden. Die
StringBuilder-Methoden sind in der Ausführung allerdings schneller, weil sie im
Gegensatz zu den StringBuffer-Methoden nicht threadsicher sind.
Die zurückgelieferten Strings können Sie in andere Strings einbauen oder direkt ausgeben.
}
}
...
/**
* Padding (Auffüllen) für String
*/
public static String strpad(String s, int length, char c, short end,
boolean cut) {
if(length < 1 || s.length() == length)
return s;
if(end == MoreString.PADDING_LEFT)
return new String(pad) + s;
else
return s + new String(pad);
}
...
Strings
Häufig müssen Strings linksseitig mit Leerzeichen aufgefüllt werden. Für diese spezielle Auf-
gabe gibt es eine überladene Version der Methode:
public static String strpad(String s, int length) {
System.out.println();
}
}
/**
* Whitespace vom Stringanfang entfernen
*/
public static String ltrim(String s) {
int len = s.length();
int i = 0;
char[] chars = s.toCharArray();
/**
* Whitespace vom Stringende entfernen
*/
public static String rtrim(String s) {
int len = s.length();
char[] chars = s.toCharArray();
Listing 41: Methoden zur links- bzw. rechtsseitigen Entfernung von Whitespace
Beide Methoden lassen sich von der String-Methode toCharArray() das Zeichenarray zurück-
liefern, das dem übergebenen String zugrunde liegt. Dieses Array gehen die Methoden in einer
while-Schleife Zeichen für Zeichen durch: ltrim() vom Anfang und rtrim() vom Ende ausge-
hend. Die Schleife wird so lange fortgesetzt, wie der Unicode-Wert der vorgefundenen Zeichen
>> Strings 111
Strings
kleiner oder gleich dem Unicode-Wert des Leerzeichens ist (dies schließt Whitespace und nicht
druckbare Sonderzeichen aus). Anschließend wird je nachdem, ob Whitespace gefunden wurde
oder nicht, ein vom Whitespace befreiter Teilstring oder der Originalstring zurückgeliefert.
Um einen String mit Hilfe dieser Methoden am Anfang oder Ende von Whitespace zu befreien,
übergeben Sie den String einfach als Argument an die jeweilige Methode und nehmen den
bearbeiteten String als return-Wert entgegen:
String trimmed = MoreString.ltrim(str);
toString()
Für Arrays liefert toString() allerdings nur den Klassennamen und den Hashcode, wie in der
Notimplementierung von Object festgelegt.
int[] ints = { 1, -312, 45, 55, -9, 7005};
System.out.println(ints); // ruft intern ints.tostring() auf
Ausgabe:
[I@187c6c7
Angesichts der Tatsache, dass man sich von der Ausgabe eines Arrays in der Regel verspricht,
dass die einzelnen Array-Elemente in der Reihenfolge, in der sie im Array gespeichert sind, in
Strings umgewandelt und aneinander gereiht werden, ist die Performance von toString() ent-
täuschend. Andererseits ist bekannt, dass die Array-Unterstützung von Java nicht in den
Array-Objekten selbst, sondern in der Utility-Klasse Arrays implementiert ist. Und richtig, es
gibt auch eine Arrays-Methode toString().
Arrays.toString()
Die Arrays-Methode toString() übernimmt als Argument ein beliebiges Array und liefert als
Ergebnis einen String mit den Elementen des Arrays zurück. Genauer gesagt: Die Elemente im
Array werden einzeln in Strings umgewandelt, durch Komma und Leerzeichen getrennt anein-
ander gehängt, schließlich in eckige Klammern gefasst und zurückgeliefert:
int[] ints = { 1, -312, 45, 55, -9, 7005};
System.out.println(Arrays.toString(ints));
Ausgabe:
[1, -312, 45, 55, -9, 7005]
112 >> Arrays in Strings umwandeln
Strings
Enthält das Array als Elemente weitere Arrays, werden diese allerdings wiederum nur durch
Klassenname und Hashcode repräsentiert (siehe oben). Sollen Unterarrays ebenfalls durch
Auflistung ihrer Elemente dargestellt werden, rufen Sie die Arrays-Methode deepToString()
auf.
Auch wenn die Array-Methode toString() der Vorstellung einer praxisgerechten Array-to-
String-Methode schon sehr nahe kommt, lässt sie sich noch weiter verbessern. Schön wäre
zum Beispiel, wenn der Programmierer selbst bestimmen könnte, durch welche Zeichen oder
Zeichenfolge die einzelnen Array-Elemente getrennt werden sollen. Und auf die Klammerung
der Array-Elemente könnte man gut verzichten.
MoreString.toString()
Die statische Methode MoreString.toString() ist weitgehend identisch zu Arrays.toString(), nur
dass der erzeugte String nicht in Klammern gefasst wird und der Programmierer selbst bestimmen
kann, durch welche Zeichenfolge die einzelnen Array-Elemente getrennt werden sollen (zweites
Argument: separator). Ich stelle hier nur die Implementierungen für int[]- und Object[]-Arrays
vor. Für Arrays mit Elementen anderer primitiver Datentypen (boolean, char, long, double etc.)
müssen nach dem gleichen Muster eigene überladene Versionen geschrieben werden:
/**
* int[]-Arrays in Strings umwandeln
*/
public static String toString(int[] a, String separator) {
if (a == null)
return "null";
if (a.length == 0)
return "";
return buf.toString();
}
/**
* Object[]-Arrays in Strings umwandeln
*/
public static String toString(Object[] a, String separator) {
if (a == null)
return "null";
if (a.length == 0)
return "";
Strings
buf.append(a[0].toString());
return buf.toString();
}
Mit Hilfe dieser Methoden ist es ein Leichtes, die Elemente eines Arrays wahlweise durch Leer-
zeichen, Kommata, Semikolons oder auch Zeilenumbrüche getrennt in einen String zu ver-
wandeln:
int[] ints = { 1, -312, 45, 55, -9, 7005};
String intStr;
1. Als Erstes zerlegen Sie den String in Teilstrings. Dies geschieht am effizientesten mit der
split()-Methode (siehe Rezept 29).
Als Argument übergeben Sie einen regulären Ausdruck (in Form eines Strings), der angibt,
an welchen Textstellen der String aufgebrochen werden soll. Im einfachsten Fall besteht
dieser reguläre Ausdruck aus einem oder mehreren Zeichen respektive Zeichenfolgen, die
als Trennzeichen zwischen den informationstragenden Teilstrings stehen. Die Teilstrings
werden als String-Array zurückgeliefert.
String[] buf = aString.split(" "); // Leerzeichen als
// Trennzeichen
Strings
String[] buf = data.split("\t");
int[] numbers = new int[buf.length];
try {
for (int i = 0; i < numbers.length; ++i)
numbers[i] = Integer.parseInt(buf[i]);
} catch (NumberFormatException e) {
System.err.println(" Fehler bei Umwandlung in Integer");
}
}
}
/**
* Klasse zur Erzeugung zufälliger Zeichenketten aus dem Bereich a-Z
*/
import java.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 min, int max) {
Random randGen = new Random(System.currentTimeMillis());
ArrayList<String> result = new ArrayList<String>();
Strings
if(value >= 91 && value <= 96)
continue;
else
counter++;
result.add(curStr.toString());
}
return result;
}
}
Das Start-Programm zu diesem Rezept benutzt den String-Generator zur Erzeugung von zehn
Strings mit vier bis fünfzehn Buchstaben.
38 Wortstatistik erstellen
Das Erstellen einer Wortstatistik, d.h. das Erkennen der Wörter inklusive ihrer Häufigkeit, lässt
sich mit Java sehr elegant realisieren. Die Kombination zweier Klassen stellt alles Nötige
bereit:
왘 Mit java.util.StringTokenizer wird der Text in die interessierenden Wörter zerlegt.
Bequemerweise kann man der Klasse auch einen String mit allen Zeichen mitgeben, die als
Trennzeichen interpretiert werden sollen. Dadurch kann man beispielsweise neben dem
üblichen Leerzeichen auch andere Satzzeichen beim Lesen überspringen.
왘 Alle gefundenen Wörter werden in einer Hashtabelle (z.B. java.util.HashMap) abgespei-
chert, zusammen mit ihrer aktuellen Häufigkeit. Wenn ein Wort bereits vorhanden ist, wird
lediglich der alte Zählerwert um eins erhöht.
/**
* Klasse zur Erstellung von Wortstatistiken
*/
import java.util.*;
import java.io.*;
class WordStatistics {
/**
* Erstellt eine Wortstatistik; Rückgabe ist eine Auflistung aller
* vorkommenden Wörter und ihrer Häufigkeit; Satzzeichen und gängige
* Sonderzeichen werden ignoriert.
*
* @param text zu analysierender Text
* @return HashMap<String, Integer> mit Wörtern und ihren Häufigkeiten
*/
public static HashMap<String, Integer> countWords(String text) {
StringTokenizer st = new StringTokenizer(text,
"\n\" -+,&%$§.;:?!(){}[]");
HashMap<String, Integer> wordTable = new HashMap<String, Integer>();
while(st.hasMoreTokens()) {
String word = st.nextToken();
Integer num = wordTable.get(word);
if(num == null) {
// bisher noch nicht vorhanden -> neu einfügen mit Zählwert = 1
num = new Integer(1);
wordTable.put(word, num);
}
else {
// Wort bereits vorhanden -> Zähler erhöhen
int numValue = num.intValue() + 1;
num = new Integer(numValue);
wordTable.put(word, num);
}
Strings
}
return wordTable;
}
}
if(args.length != 1) {
System.out.println("Aufruf: <Dateiname>");
System.exit(0);
}
// Datei einlesen
StringBuilder text = new StringBuilder();
try {
BufferedReader reader = new BufferedReader(
new FileReader(args[0]));
String line;
} catch(Exception e) {
e.printStackTrace();
}
// Statistik erstellen
HashMap<String, Integer> statistic;
statistic = WordStatistics.countWords(text.toString());
// Statistik ausgeben
System.console().printf("\n Anzahl unterschiedlicher Wörter: %d\n",
statistic.size());
Set<String> wordSet = statistic.keySet();
Iterator<String> it = wordSet.iterator();
while(it.hasNext()) {
String word = it.next();
int num = statistic.get(word).intValue();
System.console().printf(" %s : %d \n", word, num); }
}
}
}
Die Gesamtzahl an unterschiedlichen Wörtern kann man wie oben gezeigt über die
H in we is
Methode size() der Hashtabelle ermitteln. Falls man allerdings die gesamte Anzahl an
Wörtern (inklusive Wiederholungen) benötigt, bietet StringTokenizer die Methode
countTokens() an:
StringTokenizer st = new StringTokenizer(meinText, "\n\" -+,&%$§.;:?!(){}[]");
System.out.println("Gesamtzahl Wörter: " + st.countTokens());
Datum und Uhrzeit
Leider gibt es derzeit nur drei vordefinierte Kalenderklassen, die von getInstance() zurückge-
liefert werden: sun.util.BuddhistCalendar für die Thai-Lokale (th_TH), JapaneseImperial-
Calendar (für ja_JP) und GregorianCalendar für alle anderen Lokalen.
Trotzdem sollten Sie den Empfehlungen von Sun folgen und die Klasse Date nur dann verwen-
den, wenn Sie an der reinen Systemzeit interessiert sind oder die chronologische Reihenfolge
verschiedener Zeiten prüfen wollen. Wenn Sie explizit mit Datumswerten programmieren
müssen, verwenden Sie Calendar oder GregorianCalendar.
// Aktuelles Datum mit Calendar abfragen
import java.util.Calendar;
Die Felder (Jahr, Monat, Stunde ...) eines Calendar-Objekts können mit Hilfe der get-/set-
Methoden der Klasse abgefragt bzw. gesetzt werden.
Methode Beschreibung
Zum Abfragen der verschiedenen Feldwerte. Die Felder
Datum und Uhrzeit
Tabelle 15: Get-/Set-Methoden zum Abfragen und Setzen der Datumsfelder der Calendar-
Klasse
Die Klasse Date enthält ebenfalls Methoden zum Abfragen und Setzen der einzelnen
A c h tun g
Datums- und Zeitfelder. Diese sind jedoch als »deprecated« eingestuft, von ihrem
Gebrauch wird abgeraten.
>> Datum und Uhrzeit 123
Für Daten vor dem 15. Oktober 1582 berechnet die Klasse GregorianCalendar das
A c h t u ng
Datum nach dem julianischen Kalender. Dies ist sinnvoll, da an diesem Tag – der dem
5. Oktober 1582 im julianischen Kalender entspricht – der gregorianische Kalender
erstmals eingeführt wurde (in Spanien und Portugal). Andere Länder folgten nach und
nach. In England und Amerika begann der gregorianische Kalender beispielsweise mit
dem 14. September 1752. Wenn Sie die Lebensdaten englischer bzw. amerikanischer
Persönlichkeiten oder Daten aus der englischen bzw. amerikanischen Geschichte, die
vor der Einführung des gregorianischen Kalender liegen, historisch korrekt darstellen
möchten, müssen Sie das Datum der Einführung mit Hilfe der Methode setGregorian-
Change(Date) umstellen.
GregorianCalendar change = new GregorianCalendar(1752, 8, 14, 1, 0, 0);
((GregorianCalendar) birthday).setGregorianChange(change.getTime());
Wenn Sie Interesse halber beliebige Daten nach dem gregorianischen Kalender berech-
nen möchten, rufen Sie setGregorianChange(Date(Long.MIN_VALUE)) auf. Wenn Sie
beliebige Daten nach dem julianischen Kalender berechnen wollen, rufen Sie setGrego-
rianChange(Date(Long.MAX_VALUE)) auf.
Der Vollständigkeit halber sei erwähnt, dass es auch die Möglichkeit gibt, ein Date-Objekt
durch Angabe von Jahr (abzgl. 1900), Monat (0-11) und Tag (1-31) zu erzeugen:
Date birthday1 = new Date(64, 4, 20);
Vom Gebrauch dieses Konstruktors wird allerdings abgeraten, er ist als deprecated markiert.
124 >> Bestimmtes Datum erzeugen
Vor der Einführung des gregorianischen Kalenders im Jahre 1582 durch Papst Gregor
XIII. galt in Europa der julianische Kalender. Der julianische Kalender, von dem ägyp-
tischen Astronomen Sosigenes ausgearbeitet und von Julius Cäsar im Jahre 46 v. Chr.
Datum und Uhrzeit
in Kraft gesetzt, war ein reiner Sonnenkalender, d.h., er richtete sich nicht nach den
Mondphasen, sondern nach der Länge des mittleren Sonnenjahres, die Sosigenes zu
365,25 Tagen berechnete. Der julianische Kalender übernahm die zwölf römischen
Monate, korrigierte aber deren Längen auf die noch heute gültige Anzahl Tage, so dass
das Jahr fortan 365 Tage enthielt. Um die Differenz zum »angenommenen« Sonnenjahr
auszugleichen, wurde alle vier Jahre ein Schaltjahr eingelegt.
Tatsächlich ist das mittlere Sonnenjahr aber nur 365,2422 Tage lang (tropisches Jahr).
Der julianische Kalender hinkte seiner Zeit also immer weiter hinterher, bis im Jahre
1582 das Primar-Äquinoktium auf den 11. statt den 21. März fiel.
Um die Differenz auszugleichen, verfügte Papst Gregor XIII. im Jahr 1582, dass in die-
sem Jahr auf den 4. Oktober der 15. Oktober folgen sollte. Gleichzeitig wurde der
gregorianische Kalender eingeführt, der sich vom julianischen Kalender in der Berech-
nung der Schaltjahre unterscheidet. Während der julianische Kalender alle vier Jahre
ein Schaltjahr einlegte, sind im gregorianischen Kalender alle Jahrhundertjahre, die
nicht durch 400 teilbar sind, keine Schaltjahre. Durch diese verbesserte Schaltregel ist
ein Jahr im gregorianischen Kalender durchschnittlich 365,2425 Tage lang, was dem
tatsächlichen mittleren Wert von 365,2422 Tagen (tropisches Jahr) sehr nahe kommt.
(Erst nach 3000 Jahren wird sich die Abweichung zu einem Tag addieren.)
Spanien, Portugal und Teile Italiens führten den gregorianischen Kalender wie vom
Papst vorgesehen in der Nacht vom 4. auf den 5./15. Oktober ein. Die meisten katholi-
schen Länder folgten in den nächsten Jahren, während die protestantischen Länder den
Kalender aus Opposition zum Papst zunächst ablehnten. Die orthodoxen Länder Ost-
europas führten den gregorianischen Kalender gar erst im 20. Jahrhundert ein.
Land Einführung
Spanien, Portugal, Teile Italiens 04./15. Oktober 1582
Frankreich 09./20. Dezember 1582
Bayern 05./16. Oktober 1583
Hzm. Preußen 22. August/02. September 1612
England, Amerika 02./14. 1752
Schweden 17. Februar/1. März 1753
Russland 31. Januar/14. Februar 1918
Griech.-Orthodoxe Kirche 10./24. März 1924
Türkei 1927
41 Datums-/Zeitangaben formatieren
Zur Formatierung von Datums- und Zeitangaben gibt es drei Wege zunehmender Komplexität,
aber auch wachsender Gestaltungsfreiheit:
왘 toString()
왘 DateFormat-Stile
왘 SimpleDateFormat-Muster
den, die passende Objekte abgeleiteter Klassen (derzeit nur SimpleDateFormat) zur Formatie-
rung von Datum, Uhrzeit oder der Kombination aus Datum und Uhrzeit zurückliefern.
Die Formatierung mit DateFormat besteht daher aus zwei Schritten:
1. Sie rufen die gewünschte Factory-Methode auf und lassen sich ein Formatierer-Objekt
zurückliefern.
import java.text.DateFormat;
»Internationalisierung«.
Format Beschreibung
G »v. Chr.« oder »n. Chr«
(in englischsprachigen Lokalen »BC« oder »AD«)
yy Jahr, zweistellig
yyyy Jahr, vierstellig
M, MM Monat, einstellig (soweit möglich) bzw. immer zweistellig (01, 02 ...)
MMM Monat als 3-Buchstaben-Kurzform
MMMM voller Monatsname
w, ww Woche im Jahr, einstellig (soweit möglich) bzw. immer zweistellig (01, 02 ...)
W Woche im Monat
D, DD, DDD Tag im Jahr, einstellig bzw. zweistellig (soweit möglich) oder immer dreistellig (001,
002 ...)
d, dd Tag im Monat, einstellig (soweit möglich) bzw. immer zweistellig (01, 02 ...)
E Wochentag-Kürzel: »Mo«, »Di«, »Mi«, »Do«, »Fr«, »Sa«, »So«
(für englischsprachige Lokale werden dreibuchstabige Kürzel verwendet)
EEEE Wochentag (ausgeschrieben)
a »AM« oder »PM«
H, HH Stunde (0-23), einstellig (soweit möglich) bzw. immer zweistellig (00, 01 ...)
h, hh Stunde (1-12), einstellig (soweit möglich) bzw. immer zweistellig (00, 01 ...)
K, KK Stunde (1-24), einstellig (soweit möglich) bzw. immer zweistellig (00, 01 ...)
k, kk Stunde (1-12), einstellig (soweit möglich) bzw. immer zweistellig (00, 01 ...)
Format Beschreibung
m, mm Minuten, einstellig (soweit möglich) bzw. immer zweistellig (00, 01 ...)
s, ss Sekunden, einstellig (soweit möglich) bzw. immer zweistellig (00, 01 ...)
Millisekunden, einstellig bzw. zweistellig (soweit möglich) oder immer dreistellig (001,
Datum und Uhrzeit
S, SS, SSS
002 ...)
z Zeitzone
Z Zeitzone (gemäß RFC 822)
Die Sprache, in der die Namen zurückgeliefert werden, legen Sie bei der Instanzierung des
DateFormatSymbols-Objekts fest. Wenn Sie den Konstruktor wie oben ohne Argument aufrufen,
wird die Sprache des aktuellen Systems verwendet.1 Wenn Sie dem Konstruktor eine Lokale
übergeben, wird die Sprache dieser Lokale verwendet.
Wenn Sie zu einem bestimmten Datum den lokalisierten Monats- oder Wochentagsna-
H in we is
men abfragen möchten, können Sie seit Java 6 dazu die Calendar-Methode getDisplay-
Name() verwenden:
String weekdayName = calendar.getDisplayName(Calendar.DAY_OF_WEEK,
Calendar.LONG,
Locale.getDefault());
String monthName = calendar.getDisplayName(Calendar.MONTH,
Calendar.LONG,
new Locale("no", "NO"));
Von der Schwestermethode getDisplayNames() können Sie sich eine Map<String, Inte-
ger>-Collection der Monats- oder Wochentagsnamen zurückliefern lassen. Allerdings
verwendet diese Collection die Namen als Schlüssel und da sie zudem ungeordnet ist,
eignet sie sich nicht zum Aufbau von Listenfeldern oder ähnlichen geordneten Auflis-
tungen der Wochentage oder Monate.
// Listenfeld konfigurieren
weekdayList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
weekdayList.addListSelectionListener(new ListSelectionListener() {
public void valueChanged(ListSelectionEvent e) {
JList list = (JList) e.getSource();
String s = (String) list.getSelectedValue();
if (s != null) {
lb3.setText("Ausgew. Wochentag: " + s);
}
}
});
1. Um exakt zu sein: Es wird die Sprache der Standardlokale verwendet. Die Standardlokale spiegelt allerdings die Sys-
temkonfiguration wider, es sei denn, Sie hätten sie zuvor umgestellt, siehe Rezept 215.
130 >> Datumseingaben einlesen und auf Gültigkeit prüfen
Listenfelder mit den Monatsnamen können Sie analog erzeugen. Sie müssen nur zum Füllen
des Listenfelds die DateFormatSymbols-Methode getMonths() aufrufen.
try {
// Datum aus Kommandozeile einlesen
date = parser.parse(args[0]);
} catch(ParseException e) {
System.err.println(" Kein gueltiges Datum (TT.MM.JJJJ)");
}
Eine Beschreibung der vordefinierten DateFormat-Formate sowie der Definition eigener For-
mate mit SimpleDateFormat finden Sie in Rezept 41. Ein Beispiel für das Einlesen von Datums-
werten mit eigenen SimpleDateFormaten finden Sie im Start-Programm zu diesem Rezept.
>> Datum und Uhrzeit 131
Beim Parsen spielt es grundsätzlich keine Rolle, wie oft ein bedeutungstragender Buch-
A c h tun g
stabe, etwa das M für Monatsangaben, wiederholt wird. Während »MM« bei der Forma-
tierung eine zweistellige Ausgabe erzwingt, sind für den Parser »M« und »MM« gleich,
d.h., er liest die Anzahl der Monate – egal aus wie vielen Ziffern die Angabe besteht.
44 Datumswerte vergleichen
Um festzustellen, ob zwei Datumswerte (gegeben als Date- oder Calendar-Objekt) gleich sind,
brauchen Sie nur die Methode equals() aufzurufen:
// gegeben Date t1 und t2
if (t1.equals(t2))
System.out.println("gleiche Datumswerte");
Um zu prüfen, ob ein Datum zeitlich vor oder nach einem zweiten Datum liegt, stehen Ihnen
neben compareTo() die speziellen Methoden before() und after() zur Verfügung:
// gegeben Calendar t1 und t2
if (t1.after(t2) )
System.out.println("\t t1 liegt nach t2");
132 >> Datumswerte vergleichen
Date-/Calendar-Methode Beschreibung
boolean equals(Object) Liefert true, wenn der aktuelle Datumswert und das übergebene
Datum identisch sind.
Bei Date ist dies der Fall, wenn beide Objekte vom Typ Date sind
Datum und Uhrzeit
return clone1.compareTo(clone2);
}
Die Methode compareDays() nutzt einen anderen Ansatz als equalDays(). Sie legt Kopien der
übergebenen Calendar-Objekte an und setzt für diese die Werte der Stunden, Minuten, Sekun-
den (set()-Aufruf) sowie Millisekunden (clear()-Aufruf) auf 0. Dann vergleicht sie die Klone
mit Calendar.compareTo() und liefert das Ergebnis zurück.
Wenn Sie mit Calendar-Objekten arbeiten, erhalten Sie die Anzahl Millisekunden von
H i n we i s
der Methode getTimeInMillis(). Für Date-Objekte rufen Sie stattdessen getTime() auf.
Uhrzeit ausschalten
Datumswerte, ob sie nun durch ein Objekt der Klasse Date oder Calendar repräsentiert werden,
schließen immer auch eine Uhrzeit ein. Wenn Sie Datumsdifferenzen ohne Berücksichtigung
der Uhrzeit berechnen wollen, müssen Sie die Uhrzeit für alle Datumswerte auf einen gemein-
samen Wert setzen.
Wenn Sie ein neues GregorianCalendar-Objekt für ein bestimmtes Datum setzen und nur die
Daten für Jahr, Monat und Tag angeben, werden die Uhrzeitfelder automatisch auf 0 gesetzt.
Um Ihre Intention deutlicher im Quelltext widerzuspiegeln, können Sie die Felder für Stunden,
Minuten und Sekunden aber auch explizit auf 0 setzen:
GregorianCalendar date1 = new GregorianCalendar(2002, 5, 1);
GregorianCalendar date2 = new GregorianCalendar(2002, 5, 1, 0, 0, 0);
Für bestehende Calendar-Objekte können Sie die Uhrzeitfelder mit Hilfe von set() oder
clear() auf 0 setzen:
// Aktuelles Datum
Calendar today = Calendar.getInstance();
134 >> Differenz zwischen zwei Datumswerten in Jahren, Tagen und Stunden berechnen
Eine Beschreibung der verschiedenen Datums- und Uhrzeitfelder finden Sie in Tabelle 15 aus
Rezept 40.
Die Klasse TimeSpan dient sowohl der Repräsentation als auch der Berechnung von Datumsdif-
ferenzen. In ihren private-Feldern speichert sie die Differenz zwischen zwei Datumswerten
sowohl in Sekunden (diff) als auch ausgedrückt als Kombination aus Jahren, Tagen, Stunden,
Minuten und Sekunden.
TimeSpan-Objekte können auf zweierlei Weise erzeugt werden:
왘 indem Sie den public-Konstruktor aufrufen und die Differenz selbst als Kombination aus
Jahren, Tagen, Stunden, Minuten und Sekunden übergeben:
TimeSpan ts = new TimeSpan(0, 1, 2, 0, 0);
왘 indem Sie die Methode getInstance() aufrufen und dieser zwei GregorianCalendar-Objekte
übergeben, sowie boolesche Werte, die der Methode mitteilen, ob bei der Berechnung der
Differenz auf Sommerzeit und Schaltjahre zu achten ist:
GregorianCalendar time1 = new GregorianCalendar(2005, 2, 26);
GregorianCalendar time2 = new GregorianCalendar(2005, 2, 27);
TimeSpan ts = TimeSpan.getInstance(time1, time2, true, true);
Die Differenz, die ein TimeSpan-Objekt repräsentiert, können Sie auf zweierlei Weise abfragen:
왘 Mit Hilfe der get-Methoden (getYears(), getDays() etc.) lassen Sie sich die Werte der zu-
gehörigen Felder zurückliefern und erhalten so die Kombination aus Jahren, Tagen, Stun-
>> Datum und Uhrzeit 135
den, Minuten und Sekunden, aus denen sich die Differenz zusammensetzt. (Die Methode
toString() liefert auf diese Weise die Differenz als String zurück – nur dass sie natürlich
direkt auf die Feldwerte zugreift.)
왘 Mittels der in-Methoden (inYears(), inWeeks() etc.) können Sie sich die Differenz ausge-
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.TimeZone;
/**
* Klasse zur Repräsentation und Berechnung von Zeitabständen
* zwischen zwei Datumsangaben
*/
public class TimeSpan {
private int years;
private int days;
private int hours;
private int minutes;
private int seconds;
private long diff;
// public Konstruktor
public TimeSpan(int years, int days, int hours,
int minutes, int seconds) {
this.years = years;
this.days = days;
this.hours = hours;
this.minutes = minutes;
this.seconds = seconds;
diff = seconds + 60*minutes + 60*60*hours
+ 24*60*60*days + 365*24*60*60*years;
}
if (s.toString().equals("") )
s.append("Kein Zeitunterschied");
return s.toString();
}
}
Am interessantesten ist zweifelsohne die Methode getInstance(), die die Differenz zwischen
zwei GregorianCalendar-Objekten berechnet, indem Sie die Differenz aus den Millisekunden-
Werten (zurückgeliefert von getTimeInMillis()) berechnet, diesen Wert durch Division mit
1000 in Sekunden umrechnet und dann durch sukzessive Modulo-Berechnung und Division in
Sekunden, Minuten, Stunden, Tage und Jahr zerlegt. Am Beispiel der Berechnung des Sekun-
denanteils möchte ich dies kurz erläutern:
Nachdem die Methode die Differenz durch 1000 dividiert hat, speichert sie den sich ergeben-
den Wert in der lokalen long-Variable diff, die somit anfangs die Differenz in Sekunden ent-
hält.
diff = (last.getTimeInMillis() - first.getTimeInMillis())/1000;
Rechnet man diff%60 (Modulo = Rest der Division durch 60), erhält man den Sekundenanteil:
int seconds = (int) (diff%60);
>> Datum und Uhrzeit 137
Anschließend wird diff durch 60 dividiert und das Ergebnis zurück in diff gespeichert.
diff /= 60;
Jetzt speichert diff die Differenz in ganzen Minuten (ohne den Rest Sekunden).
// Differenz in Sekunden
diff = (last.getTimeInMillis() - first.getTimeInMillis())/1000;
save = diff;
else
startYear = first.get(Calendar.YEAR)+1;
// Jahre berechnen
years = (int) ((diff-leapDays)/365);
// Tage berechnen
days = (int) (diff - ((years*365) + subtractLeapDays));
} else {
days = (int) (diff%365);
years = (int) (diff/365);
}
Was zum Verständnis der getInstance()-Methode noch fehlt, ist die Berücksichtigung von
Sommerzeit und Schaltjahren.
Wird für den Parameter summer der Wert true übergeben, prüft die Methode, ob einer der
Datumswerte (aber nicht beide) in die Sommerzeit der aktuellen Zeitzone fallen. Dazu lässt sie
sich von der Calendar-Methode getTimeZone() ein TimeZone-Objekt zurückliefern, das die Zeit-
zone des Kalenders präsentiert, und übergibt nacheinander dessen inDaylightTime()-Methode
die zu kontrollierenden Datumswerte:
if (summer) { // Sommerzeit ausgleichen
tz = first.getTimeZone();
if( !(tz.inDaylightTime(first.getTime()))
&& (tz.inDaylightTime(last.getTime())) )
>> Datum und Uhrzeit 139
diff += tz.getDSTSavings()/1000;
if( (tz.inDaylightTime(first.getTime()))
&& !(tz.inDaylightTime(last.getTime())) )
diff -= tz.getDSTSavings()/1000;
}
import java.util.GregorianCalendar;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.Locale;
if (args.length != 2) {
System.out.println(" Aufruf: Start <Datum: TT.MM.JJJJ> "
+ "<Datum: TT.MM.JJJJ>");
System.exit(0);
}
try {
time1.setTime(parser.parse(args[0]));
time2.setTime(parser.parse(args[1]));
ts = TimeSpan.getInstance(time1, time2, true, true);
System.out.println(" Differenz: " + ts);
Datum und Uhrzeit
} catch(ParseException e) {
System.err.println("\n Kein gueltiges Datum (TT.MM.JJJJ)");
}
}
}
Calendar-Objekte vorliegen. Diese übergeben Sie dann an die Methode getInstance(), wobei
Sie als drittes Argument unbedingt true übergeben, damit die Sommerzeit berücksichtigt wird.
(Das vierte Argument, das die Berücksichtigung der Schalttage bei der Berechnung der Jahre
steuert, ist für die Differenz in Tagen unerheblich.)
Die Realität ist leider oftmals komplizierter, als wir Programmierer es uns wünschen.
A c h t u ng
Tatsächlich ist in UTC (Coordinated Universal Time) nicht jeder Tag 24*60*60 Sekunden
lang. Alle ein oder zwei Jahre wird am Ende des 31. Dezember oder 30 Juni eine
Schaltsekunde eingefügt, so dass der Tag 24*60*60+1 Sekunden lang ist. Diese Korrek-
tur gleicht die auf einer Atomuhr basierende UTC-Zeit an die UT-Zeit (GMT) an, die auf
der Erdumdrehung beruht.
Kommt es zu einem Über- oder Unterlauf (die berechnete Anzahl Tage ist größer als die Tage
im aktuellen Monat bzw. kleiner als 1), passt add() die nächstgrößere Einheit an (für Tage also
das MONTH-Feld). Die set()-Methode passt die nächsthöhere Einheit nur dann an, wenn das
private-Feld lenient auf true steht (Standardwert, Einstellung über setLenient()). Ansonsten
wird eine Exception ausgelöst.
Datum und Uhrzeit
Mit der roll()-Methode schließlich können Sie den Wert eines Feldes ändern, ohne dass bei
Über- oder Unterlauf das nächsthöhere Feld angepasst wird.
date.roll(Calendar.DAY_OF_MONTH, days);
Methode Arbeitsweise
set(int field, int value) Anpassung der übergeordneten Einheit, wenn lenient = true,
ansonsten Auslösen einer Exception
add(int field, int value) Anpassung der übergeordneten Einheit
roll(int field, int value) Keine Anpassung der übergeordneten Einheit
Das Start-Programm demonstriert die Arbeit von add() und roll(). Datum und die hinzuzu-
addierende Anzahl Tage werden als Argumente über die Befehlszeile übergeben.
Wenn Sie das Datum mittels einer DateFormat-Instanz in einen String umwandeln
Achtung
möchten, müssen Sie beachten, dass die DateFormat-Instanz standardmäßig mit einer
GregorianCalendar-Instanz arbeitet, die für Datumswerte nach Oktober 1582 mit dem
gregorianischen Kalender arbeitet. Um das korrekte julianische Datum zu erhalten,
müssen Sie dem Formatierer eine Calendar-Instanz zuweisen, die für alle Datumswerte
nach dem julianischen Kalender rechnet, beispielsweise also jul:
DateFormat dfJul = DateFormat.getDateInstance(DateFormat.FULL);
dfJul.setCalendar(jul);
System.out.println(dfJul.format(jul.getTime()));
/**
* Gregorianisches Datum in julianisches Datum unwandeln
*/
public static GregorianCalendar gregorianToJulian(GregorianCalendar c) {
Listing 52: Methoden zur Umwandlung von Datumswerten zwischen julianischem und
gregorianischem Kalender
144 >> Ostersonntag berechnen
gc.setTimeInMillis(c.getTimeInMillis());
return gc;
}
Datum und Uhrzeit
/**
* Julianisches Datum in gregorianisches Datum unwandeln
*/
public static GregorianCalendar julianToGregorian(GregorianCalendar c) {
return gc;
}
Listing 52: Methoden zur Umwandlung von Datumswerten zwischen julianischem und
gregorianischem Kalender (Forts.)
Das Start-Programm zu diesem Rezept liest ein Datum über die Befehlszeile ein und interpre-
tiert es einmal als gregorianisches und einmal als julianisches Datum, welche jeweils in ihre
julianische bzw. gregorianische Entsprechung umgerechnet werden.
51 Ostersonntag berechnen
Der Ostersonntag ist der Tag, an dem die Christen die Auferstehung Jesu Christi feiern. Gleich-
zeitig kennzeichnet er das Ende des österlichen Festkreises, der mit dem Aschermittwoch
beginnt.
Für den Programmierer ist der Ostersonntag insofern von zentraler Bedeutung, als er den
Referenzpunkt für die Berechnung der österlichen Feiertage darstellt: Aschermittwoch, Grün-
donnerstag, Karfreitag, Ostermontag, Christi Himmelfahrt, Pfingsten.
>> 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. Sonntag, der dem ersten Pessach-Vollmond folgt.« – was auf der Nördlichen Halb-
kugel dem ersten Vollmond nach der Frühlings-Tag-und-Nachtgleiche entspricht.
import java.util.GregorianCalendar;
/**
* Datum des Ostersonntags im gregorianischen Kalender berechnen
*/
public static GregorianCalendar eastern(int year) {
int c = year/100;
int n = year - 19 * (year/19);
int k = (c - 17)/25;
Wenn Sie historische Ostertermine berechnen, müssen Sie bedenken, dass der grego-
Achtung
rianische Kalender erst im Oktober 1582, in vielen Ländern sogar noch später, siehe
Tabelle 16 in Rezept 41, eingeführt wurde.
Wenn Sie zukünftige Ostertermine berechnen, müssen Sie bedenken, dass diese nur
nach geltender Konvention gültig sind. Bestrebungen, die Berechnung des Ostertermins
zu vereinfachen und das Datum auf einen bestimmten Sonntag festzuschreiben, gibt es
schon seit längerem. Bisher konnten die Kirchen diesbezüglich allerdings zu keiner
Einigung kommen.
146 >> Ostersonntag berechnen
Kalender an, siehe Tabelle 16 in Rezept 41, sondern behielten diesen für die Berechnung des
Ostersonntags sogar noch bis heute bei.
Die folgende Methode berechnet den Ostersonntag nach dem julianischen Kalender.
import java.util.Date;
import java.util.GregorianCalendar;
/**
* Datum des Ostersonntags im julianischen Kalender berechnen
*/
public static GregorianCalendar easternJulian(int year) {
int month, day;
int a = year%19;
int b = year%4;
int c = year%7;
return gc;
}
import java.util.Date;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.text.DateFormat;
if (args.length != 1) {
System.out.println(" Aufruf: Start <Jahreszahl>");
System.exit(0);
}
Datum und Uhrzeit
try {
year = Integer.parseInt(args[0]);
// Ostersonntag berechnen
eastern = MoreDate.eastern(year);
System.console().printf(" Ostersonntag: %s\n",
df.format(eastern.getTime()));
Fakt ist aber, dass ungefähr die Hälfte aller Feiertage beweglich sind. Da wären zum einen die
große Gruppe der Feiertage, die von Osten abhängen, dann die Gruppe der Feiertage, die von
Weihnachten abhängen, und schließlich noch der Muttertag.
Letzterer ist im Übrigen kein echter Feiertag, aber wir wollen in diesem Rezept auch die Tage
Tabelle 21: Deutsche Feiertage (gesetzliche Feiertage sind farbig hervorgehoben, regionale
Feiertage sind mit * gekennzeichnet)
150 >> Deutsche Feiertage berechnen
Tabelle 21: Deutsche Feiertage (gesetzliche Feiertage sind farbig hervorgehoben, regionale
Feiertage sind mit * gekennzeichnet) (Forts.)
Wie Sie der Tabelle entnehmen können, bereitet die Berechnung der Osterfeiertage, insbeson-
dere die Berechnung des Ostersonntags, die größte Schwierigkeit. Doch glücklicherweise
haben wir dieses Problem bereits im Rezept 51 gelöst. Die Berechnung der Feiertage reduziert
sich damit weitgehend auf die Erzeugung und Verwaltung der Feiertagsdaten. Der hier präsen-
tierte Ansatz basiert auf zwei Klassen:
왘 einer Klasse CalendarDay, deren Objekte die einzelnen Feiertage repräsentieren, und
왘 einer Klasse Holidays, die für ein gegebenes Jahr alle Feiertage berechnet und in einer
Vector-Collection speichert.
/**
* Klasse zum Speichern von Kalenderinformationen zu Kalendertagen
*/
public class CalendarDay {
Die Methode eastern(), die vom Konstruktor zur Berechnung des Ostersonntags ver-
H i n we i s
wendet wird, ist in Holidays definiert und identisch zu der Methode aus Rezept 51.
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Vector;
/**
* 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);
int day;
days.add(new CalendarDay("Neujahr",
(new GregorianCalendar(year,0,1)).getTimeInMillis(),
true, true, ""));
Datum und Uhrzeit
if (advent.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY)
advent.add(Calendar.DAY_OF_MONTH, -7);
else
advent.add(Calendar.DAY_OF_MONTH,
-advent.get(Calendar.DAY_OF_WEEK)+1);
Datum und Uhrzeit
days.add(new CalendarDay("Nikolaus",
(new GregorianCalendar(year,11,6)).getTimeInMillis(),
Damit man mit der Klasse Holidays auch vernünftig arbeiten kann, definiert sie verschiedene
Methoden, mit denen der Benutzer Informationen über die Feiertage einholen kann:
왘 CalendarDay searchDay(String name)
...
// Liefert das CalendarDay-Objekt zu einem Feiertag
public CalendarDay searchDay(String name) {
// Alternative Namen berücksichtigen
if (name.equals("Heilige drei Koenige"))
name = "Heilige drei Könige";
if (name.equals("Gruendonnerstag"))
name = "Gründonnerstag";
if (name.equals("Tag der Arbeit"))
name = "Maifeiertag";
if (name.equals("Christi Himmelfahrt"))
name = "Himmelfahrt";
if (name.equals("Vatertag"))
name = "Himmelfahrt";
if (name.equals("Tag der deutschen Einheit"))
name = "Tag der Einheit";
if (name.equals("Sankt Martin"))
Datum und Uhrzeit
name = "Martinstag";
if (name.equals("Vierter Advent"))
name = "4. Advent";
if (name.equals("Dritter Advent"))
name = "3. Advent";
if (name.equals("Zweiter Advent"))
name = "2. Advent";
if (name.equals("Erster Advent"))
name = "1. Advent";
if (name.equals("Buss- und Bettag"))
name = "Buß- und Bettag";
if (name.equals("Bettag"))
name = "Buß- und Bettag";
if (name.equals("Weihnachtsabend"))
name = "Heiligabend";
if (name.equals("Erster Weihnachtstag"))
name = "1. Weihnachtstag";
if (name.equals("Zweiter Weihnachtstag"))
name = "2. Weihnachtstag";
for(CalendarDay d : days) {
if (name.equals(d.getName()) )
return d;
}
return null;
}
return null;
}
return false;
}
return false;
}
Zu diesem Rezept gibt es zwei Start-Programme. Beide nehmen die Jahreszahl, für die sie ein
Holidays-Objekt erzeugen, über die Befehlszeile entgegen.
왘 Mit Start1 können Sie die Feiertage eines Jahres auf die Konsole ausgeben oder in eine
Datei umleiten:
java Start1 > Feiertage.txt
왘 Mit Start2 können Sie abfragen, auf welches Datum ein bestimmter Feiertag im übergebe-
nen Jahr fällt. Der Name des Feiertags wird vom Programm abgefragt.2
Abbildung 30: Mit Start2 können Sie sich Feiertage in beliebigen Jahren2 berechnen lassen.
2. Immer vorausgesetzt, die entsprechenden Feiertage gibt es in dem betreffenden Jahr und an ihrer Berechnung hat
sich nichts geändert. (Denken Sie beispielsweise daran, dass es Bestrebungen gibt, das Osterdatum festzuschreiben.)
158 >> Ermitteln, welchen Wochentag ein Datum repräsentiert
Den Wert des Felds können Sie für ein bestehendes Calendar-Objekt date wie folgt abfragen:
int day = date.get(Calendar.DAY_OF_WEEK);
Für die Umwandlung der DAY_OF_WEEK-Konstanten in Strings (»Sonntag«, »Montag« etc.) ist es
am einfachsten, ein Array der Wochentagsnamen zu definieren und den von get(Calendar.
DAY_OF_WEEK) zurückgelieferten String als Index in dieses Array zu verwenden. Sie müssen aller-
dings beachten, dass die DAY_OF_WEEK-Konstanten den Zahlen von 1 (SUNDAY) bis 7 (SATURDAY)
entsprechen, während Arrays mit 0 beginnend indiziert werden.
Das Start-Programm zu diesem Rezept, welches den Wochentag zu einem beliebigen Datum
ermittelt, demonstriert diese Technik:
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.Locale;
System.out.println();
if (args.length != 1) {
System.out.println(" Aufruf: Start <Datum: TT.MM.JJJJ>");
System.exit(0);
}
try {
date.setTime(df.parse(args[0]));
day = date.get(Calendar.DAY_OF_WEEK);
} catch(ParseException e) {
System.err.println("\n Kein gueltiges Datum (TT.MM.JJJJ)");
}
}
}
date.isLeapYear(year);
Mit dem Start-Programm zu diesem Rezept können Sie prüfen, ob ein Jahr im gregorianischen
Kalender ein Schaltjahr ist.
Abbildung 33: 1900 ist kein Schaltjahr, weil es durch 100 teilbar ist. 2000 ist ein Schaltjahr,
obwohl es durch 100 teilbar ist, weil es ein Vielfaches von 400 darstellt.
>> Datum und Uhrzeit 161
// Jahresunterschied berechnen
age = otherDate.get(Calendar.YEAR) - birthdate.get(Calendar.YEAR);
return age;
}
Vielleicht wundert es Sie, dass die Methode so scheinbar umständlich prüft, ob der Monat im
Vergleichsjahr kleiner als der Monat im Geburtsjahr ist, oder, falls die Monate gleich sind, der
Tag im Monat des Vergleichsjahrs kleiner dem Tag im Monat des Geburtsjahrs ist. Könnte man
nicht einfach das Feld DAY_OF_YEAR für beide Daten abfragen und vergleichen?
Die Antwort ist nein, weil dann Schalttage das Ergebnis verfälschen können. Konkret: Für das
Geburtsdatum 01.03.1955 und ein Vergleichsdatum 29.02.2004 würde get(Calendar.DAY_OF_
YEAR) in beiden Fällen 60 zurückliefern. Das berechnete Alter wäre daher fälschlicherweise 50
statt 49.
Mit dem Start-Programm zu diesem Rezept können Sie berechnen, wie alt eine Person oder ein
Gegenstand heute ist. Das Geburtsdatum wird im Programmverlauf abgefragt, das Vergleichs-
datum ist das aktuelle Datum. Beachten Sie auch die Formatierung der Ausgabe mit Choice-
Format, siehe Rezept 11.
162 >> Alter aus Geburtsdatum berechnen
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.text.DateFormat;
import java.text.ChoiceFormat;
import java.text.ParseException;
Datum und Uhrzeit
import java.util.Locale;
import java.util.Scanner;
try {
System.out.print("\n Geben Sie Ihr Geburtsdatum im Format "
+ " TT.MM.JJJJ ein: ");
String input = sc.next();
date.setTime(df.parse(input));
if (age < 0) {
System.out.println("\n Sie sind noch nicht geboren");
} else {
double[] limits = {0, 1, 2};
String[] outputs = {"Jahre", "Jahr", "Jahre"};
ChoiceFormat cf = new ChoiceFormat(limits, outputs);
} catch(ParseException e) {
System.err.println("\n Kein gueltiges Datum (TT.MM.JJJJ)");
}
}
}
Sofern Sie die Uhrzeit nicht nur ausgeben oder bestenfalls noch mit anderen Uhrzeiten des
gleichen Tags vergleichen möchten, sollten Sie die Uhrzeit durch ein Calendar-Objekt (siehe
auch Rezept 39) repräsentieren.
왘 Sie können sich mit getInstance() ein Calendar-Objekt zurückliefern lassen, welches die
aktuelle Zeit (natürlich inklusive Datum) repräsentiert:
Calendar calendar = Calendar.getInstance();
왘 Sie können ein Calendar-Objekt erzeugen und auf eine beliebige Zeit setzen:
Calendar calendar = Calendar.getInstance();
왘 Sie können die Zeit aus einem Date-Objekt an ein Calendar-Objekt übergeben:
Calendar calendar = Calendar.getInstance();
calendar.set(calendar.get(Calendar.YEAR), // Datum
calendar.get(Calendar.MONTH), // beibehalten
calendar.get(Calendar.DATE),
12, 30, 1); // Uhrzeit setzen
왘 Sie können ein GregorianCalender-Objekt für eine bestimmte Uhrzeit erzeugen:
GregorianCalendar gCal =
// year, m, d, h, min, sec
new GregorianCalendar(2005, 4, 20, 12, 30, 1);
164 >> Zeit in bestimmte Zeitzone umrechnen
der 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
Datum und Uhrzeit
// Aktuelles Datum
Date today = new Date();
// 1. Zeitzone erzeugen
TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles");
Wenn die Uhrzeit in Form eines Calendar-Objekts vorliegt, gehen Sie analog vor. Sie
Hi nwei s
müssen lediglich daran denken, die Daten des Calendar-Objekts als Date-Objekt an die
format()-Methode zu übergeben: df.format(calObj.getTime()).
>> Datum und Uhrzeit 165
Die Zeit, die ein Calendar-Objekt repräsentiert, wird intern als Anzahl Millisekunden,
Achtung
die seit dem 01.01.1970 00:00:00 Uhr, GMT vergangen sind, gespeichert. Dieser Wert
wird durch die Umstellung auf eine Zeitzone nicht verändert. Es ändern sich lediglich
die Datumsfeldwerte, wie Stunden, Minuten etc., die intern aus der Anzahl Millisekun-
den unter Berücksichtigung der Zeitzone berechnet werden. Vergessen Sie dies nie, vor
allem nicht bei der Formatierung der Zeitwerte mittels DateFormat. Wenn Sie sich näm-
lich mit getTime() ein Date-Objekt zurückliefern lassen, das Sie der format()-Methode
von DateFormat übergeben können, wird dieses Date-Objekt auf der Grundlage der
intern gespeicherten Anzahl Millisekunden erzeugt. Soll DateFormat die Uhrzeit in der
Zeitzone des Calendar-Objekts formatieren, müssen Sie die Zeitzone des Calendar-
Objekts zuvor beim Formatierer registrieren:
df.setTimeZone(calendar.getTimeZone());
59 Zeitzone erzeugen
Zeitzonen werden in Java durch Objekte vom Typ der Klasse TimeZone repräsentiert. Da Time-
Zone selbst abstrakt ist, lassen Sie sich TimeZone-Objekte von der statischen Methode getTime-
Zone() zurückliefern, der Sie als Argument den ID-String der gewünschten Zeitzone übergeben:
TimeZone tz = TimeZone.getTimeZone("Europe/Berlin");
Die weit verbreiteten dreibuchstabigen Zeitzonen-Abkürzungen wie ETC, PST, CET, die
H i n we i s
aus Gründen der Abwärtskompatibilität noch unterstützt werden, sind nicht eindeutig
und sollten daher möglichst nicht mehr verwendet werden.
if (ids[i].equals(searchedID))
tz = TimeZone.getTimeZone(ids[i]);
Wenn Sie TimeZone.getTimeZone() eine ungültige ID übergeben, erhalten Sie die Greenwich-
Zeitzone (»GMT«) zurück.
return tz;
}
Das Start-Programm zu diesem Rezept zeigt den Aufruf von MoreDate.getTimeZone(), um sich
ein TimeZone-Objekt für »America/Los_Angeles« zurückliefern zu lassen:
168 >> Differenz zwischen zwei Uhrzeiten berechnen
// aus Start.java
SimpleDateFormat sdf = new SimpleDateFormat("dd. MMMM yyyy, HH:mm");
Calendar calendar = Calendar.getInstance();
TimeZone tz;
Datum und Uhrzeit
tz = MoreDate.getTimeZone("America/Los_Angeles", -28800000,
Calendar.APRIL, 1, -Calendar.SUNDAY, 7200000,
Calendar.OCTOBER, -1, Calendar.SUNDAY, 7200000,
3600000);
sdf.setTimeZone(tz);
System.console().printf("\t%s\n", sdf.format(calendar.getTime()));
Ausgabe:
04. April 2005, 05:10
// liegt die Uhrzeit von clone1 hinter time2, erhöhe Tag von time2
if (clone1.after(time2))
time2.add(Calendar.DAY_OF_MONTH, 1);
import java.util.Calendar;
/**
* Klasse zur Repräsentation und Berechnung von Zeitabständen
* zwischen zwei Uhrzeiten
Datum und Uhrzeit
*
*/
public class TimeDiff {
return td;
}
}
Wenn Sie für den dritten Parameter false übergeben oder einfach die überladene Version mit
nur zwei Parametern aufrufen, repräsentiert das zurückgelieferte Objekt die Differenz in Stun-
den, Minuten, Sekunden vom ersten Datum zum zweiten.
Wenn Sie für den dritten Parameter true übergeben, repräsentiert das zurückgelieferte Objekt
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.text.DateFormat;
GregorianCalendar time1 =
new GregorianCalendar(2005, 4, 1, 22, 30, 0);
GregorianCalendar time2 =
new GregorianCalendar(2005, 4, 3, 7, 30, 0);
System.out.println(" Zeit 1 : " + dfDateTime.format(time1.getTime()));
System.out.println(" Zeit 2 : " + dfDateTime.format(time2.getTime()));
// Berücksichtigt Datum
System.out.println("\n Differenz zw. Uhrzeiten (mit Datum)\n");
td = TimeDiff.getInstance(time1, time2);
System.out.println(" " + td.hours + " h "
+ td.minutes + " min " + td.seconds + " sec");
// Reine Uhrzeit
System.out.println("\n\n Differenz zw. Uhrzeiten (ohne Datum)\n");
Laufzeitmessungen
Wenn Sie Laufzeitmessungen durchführen, um die Performance eines Algorithmus oder einer
Methode zu testen, beachten Sie folgende Punkte:
왘 Zugriffe auf Konsole, Dateisystem, Internet etc. sollten möglichst vermieden werden.
Derartige Zugriffe sind oft sehr zeitaufwendig. Wenn Sie einen Algorithmus testen, der
Daten aus einer Datei oder Datenbank verarbeitet, messen Sie den Algorithmus unbedingt
erst ab dem Zeitpunkt, da die Daten bereits eingelesen sind. Ansonsten kann es passieren,
dass das Einlesen der Daten weit mehr Zeit benötigt als deren Verarbeitung und Sie folg-
lich nicht die Effizienz Ihres Algorithmus, sondern die der Einleseoperation messen.
왘 Benutzeraktionen sollten ebenfalls vermieden werden.
Sie wollen ja nicht die Reaktionszeit des Benutzers messen, sondern Ihren Code.
>> Datum und Uhrzeit 173
Die Methode currentTimeMillis() gibt es bereits seit dem JDK 1.0. Sie greift, ebenso wie
Date() oder Calendar.getInstance() die aktuelle Systemzeit in Millisekunden seit dem
01.01.1970 00:00:00 Uhr, GMT, ab. (Tatsächlich rufen Date() und Calendar.getIns-
Vorsicht Jitter! Viele Java-Interpreter fallen unter die Kategorie der Just-In-Time-Com-
A c htu n g
piler, insofern als sie Codeblöcke wie z.B. Methoden bei der ersten Ausführung von
Bytecode in Maschinencode umwandeln, speichern und bei der nächsten Ausführung
dann den bereits vorliegenden Maschinencode ausführen. In diesem Fall sollten Sie
eine zu beurteilende Methode unbedingt mehrfach ausführen und die erste Laufzeit-
messung verwerfen.
/**
* Klasse zum Umrechnen von Nanosekunden in Stunden, Minuten...
*
*/
public class TimeDiff {
Datum und Uhrzeit
return td;
}
}
Listing 63: Die Klasse TimeDiff zerlegt eine Nanosekunden-Angabe in höhere Einheiten.
Aufruf:
// 3. Zeitmessung auswerten (Differenz bilden und ausgeben)
long diff = end-start;
td = TimeDiff.getInstance(diff);
System.out.println(" Laufzeit: " + td.hours + " h "
+ td.minutes + " min " + td.seconds + " sec "
+ td.millis + " milli " + td.nanos + " nano");
63 Uhrzeit einblenden
In den bisherigen Rezepten ging es mehr oder weniger immer darum, die Zeit einmalig abzufra-
gen und irgendwie weiterzuverarbeiten. Wie aber sieht es aus, wenn die Uhrzeit als digitale Zeit-
anzeige 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. In diesem
Rezept erfolgen das Abfragen und das Anzeigen der Zeit weitgehend getrennt.
왘 Für das Abfragen ist eine Klasse ClockThread verantwortlich, die, wie der Name schon ver-
rät, von Thread abgeleitet ist und einen eigenständigen Thread repräsentiert.
왘 Die Anzeige der Uhr kann in einer beliebigen Swing-Komponente (zurückgehend auf die
Basisklasse JComponent) erfolgen.
import java.util.Date;
import java.text.DateFormat;
import javax.swing.JComponent;
/**
public ClockThread(JComponent c) {
this.c = c;
this.start();
}
// Uhrzeit aktualisieren
ClockThread.time = df.format(new Date());
Der Konstruktor von ClockThread speichert die Referenz auf die Anzeige-Komponente und
startet den Thread, woraufhin intern dessen run()-Methode gestartet wird (mehr zu Threads in
der Kategorie »Threads«). Die run()-Methode enthält eine einzige große while-Schleife, die so
lange durchlaufen wird, wie der Thread ausgeführt wird. In der Schleife wird die aktuelle Zeit
abgefragt, formatiert und im statischen Feld time gespeichert, von wo sie die Anzeige-Kompo-
nente mit Hilfe der public getTime()-Methode auslesen kann.
176 >> Uhrzeit einblenden
Das Start-Programm zu diesem Rezept demonstriert, wie die Uhrzeit mit Hilfe von ClockThread
in einem JPanel, hier die ContentPane des Fensters, angezeigt werden kann.
import java.awt.*;
import java.awt.event.*;
Datum und Uhrzeit
import javax.swing.*;
// Uhrzeit einblenden
g.setFont(new Font("Arial", Font.PLAIN, 18));
g.setColor(Color.blue);
g.drawString(ClockThread.getTime(), 15, 30);
}
}
public Start() {
setTitle("Fenster mit Uhrzeit");
display = new ClockPanel();
getContentPane().add(display, BorderLayout.CENTER);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Dem Fenster fällt die Aufgabe zu, den Uhrzeit-Thread in Gang zu setzen und mit der Anzeige-
Komponente zu verbinden. Beides geschieht im Konstruktor des Fensters bei der Erzeugung
des ClockThread-Objekts.
>> Datum und Uhrzeit 177
Für die Anzeige-Komponente muss eine eigene Klasse (ClockPanel) abgeleitet werden. Nur so
ist es möglich, die paintComponent()-Methode zu überschreiben und den Code zum Einblenden
der Uhrzeit aufzunehmen.
64 Umgebungsvariablen abfragen
Die java.lang.System-Klasse stellt über die Methode getProperties() eine elegante Möglich-
System
keit zum Abrufen von Umgebungsinformationen zur Verfügung. Diese Informationen können
durchlaufen werden, da sie in Form einer java.util.Properties-Instanz vorliegen.
Um die Systemvariablen durchlaufen zu können, wird im folgenden Beispiel der lokalen Vari-
ablen env eine Referenz auf die von System.getProperties zurückgelieferte Properties-Instanz
zugewiesen. Mit Hilfe von env.keys() lassen sich dann alle Schlüssel in Form einer Enumera-
tor-Instanz auslesen. Diese kann per while-Schleife durchlaufen werden.
Innerhalb der Schleife kann der aktuelle Schlüssel mit Hilfe der Methode nextElement() der
Enumerator-Instanz ermittelt werden. Deren Rückgabe liegt allerdings in Form einer Object-
Instanz vor, die deshalb noch in einen String gecastet werden muss, bevor sie weiterverwendet
werden kann.
Jetzt lässt sich der Wert der so ermittelten Systemvariablen auslesen. Dazu wird die Methode
getProperty() der Properties-Instanz genutzt, der als Parameter der Schlüssel übergeben wird.
import java.io.PrintStream;
import java.util.Enumeration;
import java.util.Properties;
/**
* Abfrage und Ausgabe von Umgebungsvariablen
*/
public static void enumerate() {
// Properties einlesen
Properties env = System.getProperties();
// Schlüssel auslesen
Enumeration keys = env.keys();
// Standardausgabe referenzieren
PrintStream out = System.out;
// Schlüssel durchlaufen
while(keys.hasMoreElements()) {
// Wert auslesen
// Daten ausgeben
out.println(String.format("%s = %s", key, value));
}
}
System
Wenn Sie den Wert eines bestimmten Schlüssels eruieren wollen, müssen Sie nicht den
H in we is
// Betriebssystem-Name auslesen
System.out.println(
String.format("OS: %s",
System.getProperty("os.name")));
// Betriebssystem-Version auslesen
System.out.println(
String.format("Version: %s",
System
System.getProperty("os.version")));
// Java-Version auslesen
System.out.println(
String.format("Java-Version: %s",
System.getProperty("java.version")));
}
}
Die nicht garantierten Elemente sind nicht auf jedem System vorhanden. Die drei Schlüssel
user.dir, user.home und user.name werden aber in jedem Fall einen Wert zurückgeben, da sie
zu den zugesicherten Systeminformationen gehören.
67 Zugesicherte Umgebungsvariablen
Java stellt eine große Anzahl Umgebungsvariablen bereit, die über System.getProperty()
System
abgerufen werden können. Nicht alle dieser Variablen sind auf jedem System verfügbar, aber
Java sichert die Existenz zumindest einiger Umgebungsvariablen zu:
Schlüssel Beschreibung
java.version Java-Version
java.vendor Anbieter
java.vendor.url Anbieter-Homepage
java.home Installationsverzeichnis
java.vm.specification.version Version der JVM-Spezifikation
java.vm.specification.vendor Anbieter der JVM-Spezifikation
java.vm.specification.name Name der JVM-Spezifikation
java.vm.version JVM-Version
java.vm.vendor JVM-Anbieter
java.vm.name JVM-Name
java.specification.version JRE-Version
java.specification.vendor JRE-Anbieter
java.specification.name JRE-Spezifikation
java.class.version Java Class Format-Version
java.class.path Klassenpfad
java.library.path Pfade, die durchsucht werden, wenn Java-Libraries geladen
werden sollen
java.io.tmpdir Temporäres Verzeichnis
java.compiler Compiler-Name
java.ext.dirs Pfade, die durchsucht werden, wenn Java-Extensions geladen
werden sollen
os.name Name des Betriebssystems
os.arch Prozessor-Architektur
os.version Version des Betriebssystems
file.separator Trenner zwischen Pfaden und Verzeichnissen
path.separator Trenner zwischen mehreren Pfaden
line.separator Zeilenumbruch-Zeichenfolge (»\n« bei Unix, »\r\n« bei Windows)
user.name Anmeldename des aktuellen Benutzers
user.home Home-Verzeichnis des aktuellen Benutzers
user.dir Aktuelles Arbeitsverzeichnis
Interessant für den produktiven Einsatz dürften die Informationen zum Betriebssystem, zum
Benutzer, zu den Pfaden und möglicherweise auch die Java-Version sein. Andere Informatio-
nen, etwa zur JRE- oder JVM-Version, werden in der Praxis nicht allzu häufig benötigt.
sollten Sie die Existenz eines Werts, den Sie mit Hilfe von System.getProperty() ermit-
teln, immer hinterfragen und auf null prüfen, bevor Sie ihn verwenden:
System
String value = System.getProperty(key);
if(null != value && value.length() > 0) {
System.out.println(String.format("Wert von %s: %s", key, value));
}
68 System-Umgebungsinformationen abrufen
Seit Java 5 besteht die Möglichkeit, auf die Umgebungsvariablen des Betriebssystems zuzu-
greifen. So kann beispielsweise das Home-Verzeichnis des aktuell angemeldeten Benutzers
ausgelesen oder die PATH-Angabe interpretiert werden.
Das Auslesen dieser Informationen geschieht mit Hilfe einer java.util.Map-Collection, die für
Schlüssel und Werte nur Strings zulässt und der mit System.getenv() eine Referenz auf die
Systemvariablen zugewiesen wird. Mittels java.util.Iterator können die Schlüssel durchlau-
fen werden. Die Methode get() der Map-Instanz env erlaubt unter Übergabe des Schlüssels als
Parameter den Abruf des referenzierten Werts:
import java.util.Map;
import java.util.Iterator;
/**
* Abfrage und Ausgabe von Systemvariablen
*/
public static void enumerate() {
// Iteratur durchlaufen
while(keys.hasNext()) {
// Schlüssel abrufen
String key = keys.next();
// Wert abrufen
String value = env.get(key);
69 INI-Dateien lesen
Zum Lesen und Schreiben von Konfigurationsdaten und Benutzereinstellungen kann die
java.util.Properties-Klasse verwendet werden. Dabei handelt es sich um eine nicht Generics-
fähige Ableitung der java.util.Hashtable-Klasse, die ihrerseits weitestgehend der java.util.
Hashmap-Klasse entspricht.
Die Properties-Klasse verwaltet Name/Wert-Paare und bietet besondere Methoden zum Laden
und Speichern der in ihr enthaltenen Werte, wodurch sie sich besonders für die Sicherung von
Anwendungseinstellungen in Form von INI-Dateien oder XML eignet.
import java.util.Properties;
import java.io.FileInputStream;
System
import java.io.IOException;
/**
* Lädt die in der angegebenen Datei gespeicherten Parameter
*/
private static Properties load(String filename) {
// Properties-Instanz erzeugen
Properties props = new Properties();
try {
// FileInputStream-Instanz zum Laden der Daten
FileInputStream in = new FileInputStream(filename);
// Daten laden
props.load(in);
// Aufräumen
in.close();
} catch (IOException e) {
// Eventuell aufgetretene Ausnahmen abfangen
}
// Ergebnis zurückgeben
return props;
}
// Daten laden
Properties props = load("app.ini");
// ...und ausgeben
System.out.println(
String.format("Name: %s", props.getProperty("Name")));
System.out.println(
String.format("First name: %s", props.getProperty("FirstName")));
System.out.println(
String.format("City: %s", props.getProperty("City")));
}
}
import java.util.Properties;
import java.io.FileInputStream;
import java.io.IOException;
/**
* Lädt die in der angegebenen XML-Datei gespeicherten Parameter
*/
private static Properties load(String filename) {
// Properties-Instanz erzeugen
Properties props = new Properties();
try {
// FileInputStream-Instanz zum Laden der Daten
FileInputStream in = new FileInputStream(filename);
// Daten laden
props.loadFromXML(in);
} catch (IOException e) {
// Eventuell aufgetretene Ausnahmen abfangen
} finally {
// Aufräumen
in.close();
}
// Ergebnis zurückgeben
return props;
}
// Daten laden
Properties props = load("app.xml");
// ...und ausgeben
System.out.println(
String.format("Name: %s", props.getProperty("Name")));
System.out.println(
String.format("First name: %s", props.getProperty("FirstName")));
System.out.println(
System
70 INI-Dateien schreiben
Zum Speichern einer Properties-Instanz kann deren Methode store() verwendet werden.
Dabei kann eine IOException auftreten, weshalb das Speichern in einen try-catch-Block
gefasst werden sollte:
import java.util.Properties;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* Speichert eine Properties-Datei unter dem angegebenen Namen
*/
public static void save(
Properties props, String path, String comment) {
try {
// FileOutputStream-Instanz zum Speichern instanzieren
FileOutputStream fos = new FileOutputStream(path);
// Speichern
props.store(fos, comment);
// Aufräumen
fos.close();
} catch (IOException ignored) {}
}
// Werte setzen
props.setProperty("Name", "Mustermann");
props.setProperty("FirstName", "Hans");
props.setProperty("City", "Musterstadt");
// Speichern
save(props, "data.ini", "Saved data");
}
}
Der zweite Parameter der überladenen Methode store() dient der Speicherung eines Kommen-
tars – hier könnte beispielsweise auch ein Datum ausgegeben werden.
es zu einer IOException kommen (etwa wenn auf die Datei nicht schreibend zugegriffen wer-
den konnte), die mit Hilfe eines try-catch-Blocks abgefangen werden sollte:
import java.util.Properties;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* Speichert eine Properties-Datei unter dem angegebenen Namen
* als XML
*/
public static void saveAsXml(
Properties props, String path, String comment) {
try {
// FileOutputStream-Instanz zum Speichern instanzieren
FileOutputStream fos = new FileOutputStream(path);
// Speichern
props.storeToXML(fos, comment);
// Aufräumen
fos.close();
} catch (IOException ignored) {}
}
// Werte setzen
props.setProperty("Name", "Mustermann");
props.setProperty("FirstName", "Hans");
props.setProperty("City", "Musterstadt");
Das Starten von externen Prozessen ist sehr stark plattformabhängig und verstößt
A cht un g
somit gegen einen der Java-Grundsätze: »Write once, run anywhere« ist beim Ausfüh-
System
ren externer Prozesse nicht mehr gegeben.
Bei der Ausführung von Prozessen kann es zu IOExceptions kommen, falls Rechte aus dem
aktuellen Kontext heraus fehlen, 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. Alter-
nativ – und das wird in der Praxis häufiger vorkommen – kann auf die Beendigung eines Pro-
zesses mit waitFor() gewartet werden.
Die Ausgabe kann über die Methode getInputStream() in Form einer java.io.InputStream-
Instanz abgerufen werden. Sinnvollerweise wird dies in einer java.io.BufferedInputStream-
Instanz gekapselt. Das eigentliche Auslesen erfolgt mit Hilfe einer java.io.BufferedReader-
Instanz, die per zugrunde liegendem java.io.InputStreamReader die im BufferedInputStream
vorliegenden Daten verarbeitet. Auf diese Weise kann auch gleich sichergestellt werden, dass
Umlaute korrekt eingelesen werden: Der Konstruktor des InputStreamReaders erlaubt zu die-
sem und anderen Zwecken die Angabe des zu verwendenden Zeichensatzes.
import java.io.*;
/**
* Pingt die angegebene Adresse an
*/
public static String ping(String address) {
StringBuffer result = new StringBuffer();
BufferedReader rdr = null;
PrintWriter out = null;
// Runtime-Instanz erzeugen
Runtime r = Runtime.getRuntime();
try {
// Prozess erzeugen
// Syntax für Unix-Systeme: "ping -c 4 -i 1 <Adresse>"
// Syntax für Windows-Systeme: "ping <Adresse>"
Process p = r.exec(String.format("ping %s", address));
// Daten einlesen
String line = null;
while(null != (line = rdr.readLine())) {
if(line.length() > 0) {
result.append(line + "\r\n");
}
}
} catch (IOException e) {
// IOException abfangen
e.printStackTrace();
} catch (InterruptedException e) {
// Ausführung wurde unterbrochen
e.printStackTrace();
} finally {
try {
// Aufräumen
rdr.close();
} catch (IOException e) {}
}
return result.toString();
}
}
Für die Ausgabe der zurückgelieferten Prozessdaten auf die Konsole bietet sich die printf()-
Methode der Klasse Console an. Dann müssen Sie sich um eventuell enthaltene Umlaute keine
Sorgen machen.
import java.io.*;
// Pingen
Listing 74: Ausführen eines externen Prozesses und Ausgeben der vom Prozess
zurückgelieferten Daten
>> System 191
// Prozessdaten ausgeben
System.console().printf("%s%n", output);
}
}
System
Listing 74: Ausführen eines externen Prozesses und Ausgeben der vom Prozess
zurückgelieferten Daten (Forts.)
Beim Ausführen der Klasse können Sie als Parameter einen Server-Namen oder eine IP-
Adresse übergeben.
// In KBytes umrechnen
double kBytes = ((mem / 1024) * 100) / 100;
// In MBytes umrechnen
// Ausgeben
System.out.println(
String.format(
"Diese Java-Instanz hat %d Byte "
System
Die ebenfalls verfügbaren Methoden maxMemory() und totalMemory() können genutzt werden,
um den maximal verfügbaren Speicher und die Gesamtmenge an Speicher der Java-Anwen-
dung auszuwerten.
Parameter Bedeutung
-Xms<Größe> Anfänglicher Speicher für die Ausführung der Anwendung. Der Parameter
<Größe> kann dabei in Byte, 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.
-Xmx<Größe> 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 zwi-
schen 16 und 64 Mbyte.
75 DLLs laden
Das Laden und Ausführen externer Bibliotheken erfolgt via JNI (Java Native Interface). Dabei
wird die externe Bibliothek mit Hilfe von loadLibrary() in eine Java-Klasse eingebunden, ihre
Methoden werden aus Sicht der Klasse wie gewöhnliche Instanz-Methoden verwendet.
>> System 193
Das Laden und Verwenden externer Bibliotheken (auf Windows-Systemen meist als
A c h tun g
DLLs vorliegend) sollte mit Bedacht vorgenommen werden, denn es hebt die Plattform-
und Systemunabhängigkeit von Java auf.
Die grundsätzliche Vorgehensweise für den Einsatz von JNI sieht so aus:
System
왘 Erstellen einer Java-Klasse, die die zu implementierenden Funktionen definiert und die
externe Bibliothek lädt
왘 Kompilieren der Java-Klasse
왘 Erzeugen einer C/C++-Header-Datei, die die zu implementierenden Funktionen für C- 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. Die Angabe der Dateiendung der externen
Bibliothek unterbleibt dabei, so dass hier eine gewisse Portabilität gewahrt bleibt:
Nach dem Kompilieren der Klasse kann eine C/C++-Header-Datei erzeugt werden, in der die zu
implementierende JNI-Methode definiert ist. Dies geschieht unter Verwendung des Hilfspro-
gramms javah, das sich im /bin-Verzeichnis der JDK-Installation befindet.
Der Aufruf von javah sieht zur Generierung einer Datei HelloWorld.h wie folgt aus:
javah -jni -classpath "%CLASSPATH%;." -o HelloWorld.h Start
Die so erzeugte Header-Datei kann nun verwendet werden, um eine externe Bibliothek zu
erstellen. Diese wird in der Regel meist nur eine Wrapper-Funktion haben und somit den
194 >> DLLs laden
Zugriff auf andere Bibliotheken oder Systemfunktionen erlauben. Innerhalb der Header-Datei
ist eine Funktion definiert, die implementiert werden muss:
JNIEXPORT jstring JNICALL Java_Start_sayHello(JNIEnv *, jobject);
Am Beispiel eines Visual C++-Projekts soll aufgezeigt werden, wie die Umsetzung stattfinden
kann. Analog kann auch bei Verwendung eines anderen Entwicklungstools und eines anderen
Compilers vorgegangen werden.
System
Zunächst soll ein neues C++-Projekt im Visual Studio .NET angelegt werden. Dieses Projekt ist
vom Typ Windows-32-Applikation. In den Projekteigenschaften muss als Ausgabetyp »Dyna-
mische Bibliothek (.dll)« festgelegt werden.
Nach dem Erzeugen des Projekts müssen unter EXTRAS/OPTIONEN/PROJEKTE/VC++-VERZEICH-
NISSE die Ordner %JAVA-HOME%/include und %JAVA_HOME%/include/win32 für Include-
Dateien hinzugefügt werden:
In den Projekteigenschaften müssen unter dem Punkt ALLGEMEIN folgende Einstellungen vor-
genommen werden:
왘 Verwendung von ATL: Dynamische Verknüpfung zu ATL
왘 Zeichensatz: Unicode
Unter dem Punkt LINKER muss die Bibliothek %JAVA_HOME%/lib/jawt.lib hinzugefügt werden.
Die Wrapper-Klasse HelloWorld kapselt den Zugriff auf die eigentlich verwendete Klasse Say-
Hello, die folgende Header-Definition besitzt:
class SayHello {
public:
SayHello(void);
~SayHello(void);
char* execute(void);
};
System
Die Implementierung ist in diesem Fall trivial:
#include "StdAfx.h"
#include ".\sayhello.h"
// Konstruktor
SayHello::SayHello(void) {}
// Destruktor
SayHello::~SayHello(void) {}
Die Wrapper-Klasse HelloWorld.cpp muss die von Java generierte Header-Datei HelloWorld.h
referenzieren und die dort definierte Methode Java_Start_sayHello() implementieren:
// Includes
#include "stdafx.h"
#include "HelloWorld.h"
#include "SayHello.h"
#include <win32\jawt_md.h>
// Default-Einstiegspunkt
BOOL APIENTRY DllMain(HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved) {
return TRUE;
}
// Instanz erstellen
SayHello *instance = new SayHello();
// Rückgabe abrufen
const char* tmp = instance->execute();
// Größe bestimmen
size_t size = strlen(tmp);
System
// jchar-Array erzeugen
jchar* jc = new jchar[size];
// Speicher reservieren
memset(jc, 0, sizeof(jchar) * size);
// Kopieren
for(size_t i = 0; i < size; i++) {
jc[i] = tmp[i];
}
// Rückgabe erzeugen
jstring result = (jstring)env->NewString(jc, jsize(size));
// Aufräumen
delete [] jc;
// Zurückgeben
return result;
}
JNI definiert einige Datentypen, die die C-/C++-Gegenstücke zu den Java-Datentypen darstel-
len. C-/C++-Datentypen müssen stets aus und in die JNI-Datentypen gecastet werden, da sich
sowohl Größe als auch Kodierung der repräsentierten Java-Datentypen deutlich von ihren
C++-Pendants unterscheiden. Die hier praktizierte Rückgabe von Zeichenketten erfordert bei-
spielsweise, dass einzelne Zeichen in ihre JNI-jchar-Pendants gecastet werden müssen.
Achten Sie darauf, nicht mehr benötigte Ressourcen wieder freizugeben, um keine
A c h t u ng
Speicherlöcher zu erzeugen.
Nach dem Kompilieren kann die Bibliothek verwendet werden. Dabei muss sie sich innerhalb
eines durch die Umgebungsvariable PATH definierten Pfads befinden, um gefunden zu werden:
System
sonstwie unterbrochen wird. In diesem Fall wird eine InterruptedException geworfen, die auf-
gefangen oder deklariert werden muss:
import java.util.Date;
// Thread pausieren
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
77 Timer verwenden
Mit Hilfe der java.util.Timer-Klasse können Aufgaben wiederholt ausgeführt werden. Klas-
sen, die regelmäßig eingebunden werden sollen, müssen von der Basisklasse java.util.Timer-
Task erben und deren run()-Methode überschreiben:
import java.util.TimerTask;
import java.util.Calendar;
import java.text.DateFormat;
/**
Listing 81: Die Klasse SimpleTimerTask definiert einen per Timer auszuführenden Task.
198 >> Timer verwenden
running = true;
}
}
Listing 81: Die Klasse SimpleTimerTask definiert einen per Timer auszuführenden Task. (Forts.)
Das Einbinden eines TimerTask geschieht über eine neue Timer-Instanz, deren schedule()-
Methode eine Instanz der TimerTask-Ableitung als Parameter übergeben wird. Weiterhin kann
angegeben werden, wann oder mit welcher Startverzögerung und in welchem Abstand die
Ausführung stattfinden soll. Die Angabe von Startverzögerung und Ausführungsintervall
erfolgt dabei stets in Millisekunden:
import java.util.Timer;
// Timer instanzieren
Timer timer = new Timer();
// Task planen
timer.schedule(task, 0, 5000);
}
}
Beachten Sie, dass die Ausführung des Programms so lange fortgesetzt wird, wie der
A c h t u ng
Timer aktiv ist. Nach dem Planen des Tasks kann allerdings mit anderen Aufgaben
fortgefahren werden – der Timer verhält sich wie ein eigenständiger Thread, was er
intern auch ist.
>> System 199
System
Abbildung 41: Ausführung des Timers
Neben der hier gezeigten Variante existieren noch weitere Überladungen der schedule()-
Methode:
Überladung Beschreibung
schedule(TimerTask task, Date time) Führt den Task zur angegebenen Zeit aus. Es findet
keine Wiederholung der Ausführung statt.
schedule(TimerTask task, Date firstTime, Führt den Task zur angegebenen Zeit aus und wartet
long period) vor einer erneuten Ausführung die in period angege-
bene Zeitspanne in Millisekunden ab.
schedule(TimerTask task, long delay) Führt den Task nach der in Millisekunden angegebe-
nen Zeitspanne aus. Es findet keine Wiederholung der
Ausführung statt.
import java.util.Timer;
// Timer instanzieren
Timer timer = new Timer();
// Task planen
timer.scheduleAtFixedRate(task, 0, 5000);
}
}
Listing 83: Ausführen eines Timers, der exakt alle fünf Sekunden läuft
import java.util.Timer;
// Task planen
timer.scheduleAtFixedRate(task, 0, 1000);
80 Timer beenden
Timer beenden sich in der Regel, wenn die letzte Referenz auf den Timer entfernt und alle aus-
stehenden Aufgaben ausgeführt worden sind. Dies kann je nach Programmierung einige Zeit
dauern oder bei endlos laufenden Timern schier unmöglich sein.
Aus diesem Grund verfügt die Klasse Timer über die Methode cancel(), die alle noch anstehen-
den und nicht bereits ausführenden Aufgaben abbricht und den Timer beendet:
System
import java.util.Timer;
// Timer instanzieren
Timer timer = new Timer();
// Task planen
timer.scheduleAtFixedRate(task, 0, 1000);
// Timer beenden
timer.cancel();
}
}
kann ein Java-Programm beliebige eigene Knoten durch Aufruf der Methode node() generie-
ren und diesen dann Schlüssel mit Werten zuweisen bzw. lesen. Dies erfolgt wie bei einer
Hashtabelle mit den Methoden put() und get(). Für spezielle Datentypen wie boolean oder int
stehen auch besondere putXXX()-Methoden bereit, z.B. putBoolean().
import java.util.prefs.*;
System
} catch(Exception e) {
e.printStackTrace();
}
}
}
Der Einsatz des Pakets java.util.prefs erlaubt wie bereits erwähnt nur das Ablegen oder Aus-
lesen von Informationen, die von einem Java-Programm stammen. Es ist nicht möglich,
andere Bereiche der Windows-Registry zu lesen oder zu verändern.
Die Bibliothek stellt zwei zentrale Klassen bereit: RegistryKey repräsentiert einen Schlüssel
und RegistryValue steht für einen Schlüsselwert. Hierbei gibt es verschiedene Datentypen, die
als Konstanten definiert sind, u.a. ValueType.REG_SZ (null-terminated String = endet mit dem
Zeichen '\0'), ValueType.REG_BINARY (Binärdaten) und ValueType.REG_DWORD (32 Bit Integer).
Das Setzen bzw. Lesen von Registry-Werten erfolgt mit Hilfe der RegistryKey-Methoden set-
Value() bzw. getValue().
System
Das folgende Beispiel demonstriert den Einsatz dieser Klassen:
/**
* Klasse für den Zugriff auf die Windows-Registry via JNI
*/
import ca.beq.util.win32.registry.*;
import java.util.*;
class WindowsRegistry {
/**
* Erzeugt einen neuen Schlüssel
*
* @param root Schlüssel-Wurzel, z.B. RootKey.HKEY_CURRENT_USER
* @param name voller Pfad des Schlüssels
* (z.B. "Software\\Carpelibrum\\ProgData")
* @return RegistryKey-Objekt ode null bei Fehler
* (z.B. schon vorhanden)
*/
public RegistryKey createKey(RootKey root, String name) {
try {
RegistryKey key = new RegistryKey(root, name);
key.create();
return key;
} catch(Exception e) {
e.printStackTrace();
return null;
}
}
/**
* Erzeugt einen neuen Unterschlüssel
* @param root Schlüssel-Wurzel, z.B. RootKey.HKEY_CURRENT_USER
* @param parent voller Pfad des Vaterschlüssels
* (z.B."Software\\Carpelibrum")
* Vaterschlüssel muss existieren
* @param name Name des Unterschlüssels (z.B. "ProgData")
* @return RegistryKey-Objekt oder null bei Fehler
*/
public RegistryKey createSubKey(RootKey root,String parent,String name) {
try {
RegistryKey key = new RegistryKey(root, parent);
RegistryKey sub = key.createSubkey(name);
return sub;
} catch(Exception e){
e.printStackTrace();
return null;
}
System
/**
* Löscht einen Schlüssel
*/
public boolean deleteKey(RootKey root, String name) {
try {
RegistryKey key = new RegistryKey(root, name);
key.delete();
return true;
} catch(Exception e) {
e.printStackTrace();
return false;
}
}
/**
* Liefert den gewünschten Schlüssel
*/
public RegistryKey getKey(RootKey root, String name) {
RegistryKey result = null;
try {
result = new RegistryKey(root, name);
if(result.exists() == false)
result = null;
} catch(Exception e) {
e.printStackTrace();
}
return result;
}
/**
* Liefert alle Unterschlüssel zu einem Schlüssel
*/
public ArrayList<RegistryKey> getSubkeys(RootKey root, String name) {
ArrayList<RegistryKey> result = new ArrayList<RegistryKey>();
try {
RegistryKey key = new RegistryKey(root, name);
if(key.hasSubkeys()) {
Iterator it = key.subkeys();
while(it.hasNext()) {
RegistryKey rk = (RegistryKey) it.next();
result.add(rk);
System
}
}
} catch(Exception e) {
e.printStackTrace();
}
return result;
}
}
Das Start-Programm zu diesem Rezept demonstriert den Zugriff auf die Windows Registry. Es
liest alle Unterschlüssel von HKEY_CURRENT_USER\Software aus und trägt einen eigenen Schlüssel
ein.
import ca.beq.util.win32.registry.*;
import java.util.*;
try {
WindowsRegistry registry = new WindowsRegistry();
if(key == null)
key = registry.createKey(RootKey.HKEY_CURRENT_USER,
"Software\\Carpelibrum");
// Werte setzen
RegistryValue name = new RegistryValue("Name", "Mustermann");
RegistryValue anzahl = new RegistryValue("Anzahl", 5);
key.setValue(name);
key.setValue(anzahl);
System
while(it.hasNext()) {
RegistryValue value = (RegistryValue) it.next();
System.out.println(value.getName() + " : " +
value.getStringValue());
}
}
} catch(Exception e) {
e.printStackTrace();
}
}
}
Hook und wird als Letztes ausgeführt, und zwar sowohl bei einem regulären Programmende
als auch einem vorzeitig erzwungenen Ende durch Beenden der VM über die Tastenkombina-
tion (Strg)+(C).
Das Beenden per Taskmanager unter Windows wird leider nicht erkannt. Unter Unix/
H in we is
Linux werden alle Beendigungen erkannt, die das Signal SIGINT auslösen.
System
Der Einsatz des Hook-Mechanismus ist sehr einfach. Man definiert eine eigene Thread-Klasse,
die man mit Hilfe der Methode Runtime.addShutdownHook(Thread t) registriert. Das war es
schon. Falls man nur für bestimmte Programmphasen einen Hook als Rückversicherung haben
möchte, kann man jederzeit mit Runtime.removeShutdownHook(Thread t) den Hook wieder
abmelden. Dies ist beispielsweise praktisch, falls der Hook nur bei einem vorzeitigen Abbruch
abgearbeitet werden soll, aber nicht bei einem regulären Programmende. In diesem Fall sollte
die letzte Anweisung der Aufruf von removeShutdownHook() sein.
/**
* Modellklasse für ShutdownHook zum Abfangen von STRG-C
*/
class ShutdownHook extends Thread {
private Start parent;
public ShutdownHook(Start t) {
parent = t;
}
/**
* Aufrufbeispiel: STRG-C führt zur Ausgabe des letzten Wertes
*/
public class Start {
private int value = 0;
rt.addShutdownHook(hook);
try {
System
Thread.sleep(500);
} catch(Exception e) {
}
}
83 Betriebssystem-Signale abfangen
Ein Betriebssystem kann jedem laufenden Programm Signale senden. Eines der wichtigsten
Signale ist SIGINT, das einen Programmabbruch signalisiert, wie er auf Windows-Rechnern
beispielsweise ausgelöst wird, wenn innerhalb eines Konsolenfensters die Tastenkombination
(Strg)+(C) gedrückt wird (woraufhin die Java Virtual Machine und damit auch das aus-
geführte Java-Programm abgebrochen werden). Rezept 82 zeigte, wie Sie mit Hilfe eines so
genannten Shutdown-Hooks noch Aufräumarbeiten oder Speicheraktionen etc. durchführen,
bevor das Programm zwangsweise beendet wird. Was aber, wenn das Programm einfach wei-
terarbeiten und das Betriebssystem-Signal ignorieren soll? Dann hilft auch kein Shutdown-
Hook.
>> System 209
Als Lösung bietet Sun zwei inoffizielle Klassen an: sun.misc.Signal sowie sun.misc.Signal-
Handler. Diese Klassen erscheinen in keiner Dokumentation, sie sind offiziell nicht vorhanden,
und es gibt daher keine Garantie dafür, dass sie in zukünftigen Java-Versionen weiterhin vor-
handen sein werden. Eine entsprechende Warnung wird beim Kompilieren ausgegeben. Das
folgende Beispiel demonstriert das Abfangen des Signals SIGINT:
import sun.misc.Signal;
System
import sun.misc.SignalHandler;
int counter = 0;
} catch(Exception e) {
}
}
}
}
Zur Einrichtung einer Signalbehandlung rufen Sie die statische Methode Signal.handle() auf,
die zwei Parameter erwartet:
왘 Eine Instanz der Klasse Signal, der Sie den Namen des zu fangenden Signals übergeben.
Der Name ist dabei der Betriebssystem-typische Signalname ohne »SIG«, also z.B. INT für
SIGINT.
왘 Eine Instanz der Klasse SignalHandler mit einer Implementierung der Methode handle().
210 >> Betriebssystem-Signale abfangen
System
Formatierte Ausgabe
Typ Beschreibung
c Darstellung als Unicode-Zeichen
d Dezimal: Integer zur Basis 10
x Hexadezimal: Integer zur Basis 16
f Gleitkommazahl
s String
t Zeit/Datum; auf t folgt ein weiteres Zeichen:
H (Stunde), M (Minute), S (Sekunde), d (Tag), m (Monat), Y (Jahr), D (Datum als Tag-
Monat-Jahr)
% Darstellung des Prozentzeichens
import java.util.Date;
int index = 4;
float f= 3.75f;
String txt = "Wahlanteil";
Date dt = new java.util.Date();
System.out.println();
Ein- und Ausgabe
// Ausgabe
if (cons != null) {
cons.printf("\n");
cons.printf(" Ausgabe der Umlaute mit Console \n");
cons.printf(" ä, ö, ü, ß \n");
}
}
}
Für vereinzelte Ausgaben lohnt es sich nicht, den Verweis auf das Console-Objekt in
T ip p
einer eigenen Variablen zu speichern. Hängen Sie in solchen Fällen den printf()-Auf-
ruf einfach an den System.console()-Aufruf an:
System.console().printf("\n Ausgabe der Umlaute mit Console \n");
System.console().printf(" ä, ö, ü, ß \n");
import java.io.*;
PrintStream out;
214 >> Von der Konsole (Standardeingabe) lesen
try {
out = new PrintStream(System.out, true, "Cp850");
} catch (UnsupportedEncodingException e) {
out = System.out;
}
out.printf("\n");
out.printf(" Ausgabe der Umlaute mit PrintStream \n");
out.printf(" ä, ö, ü, ß \n");
}
}
왘 ein OutputStream-Objekt, das das Ziel der Ausgabe vorgibt (hier die Konsole)
왘 true, damit die Ausgaben sofort ausgeführt werden (andernfalls werden die Ausgaben
gepuffert, und Sie müssen die Methode flush() aufrufen)
왘 den Namen der gewünschten Zeichenkodierung (hier "Cp850" für die DOS-Codepage 850)
Vor Java 6 war dies der übliche Weg, um Umlaute auf die Konsole auszugeben.
H inwe is
try {
System.out.print(" Geben Sie Ihren Namen ein: ");
name = in.readLine();
System.out.print(" Geben Sie Ihr Alter ein: ");
age = Integer.parseInt( in.readLine() );
Umlaute, die über die Tastatur eingelesen wurden, werden bei Ausgabe auf die Konsole
H in we is
mit System.out korrekt angezeigt. Probleme gibt es allerdings, wenn Sie die eingelese-
nen Strings in eine Datei schreiben oder in eine grafische Benutzeroberfläche ein-
bauen. Dann sollten Sie entweder auf Console umsteigen (siehe unten) oder den
InputStreamReader mit passender Zeichenkodierung erzeugen.
Umlaute, die über die Tastatur eingelesen wurden, werden bei Ausgabe auf die Konsole
H in we is
mit System.out korrekt angezeigt. Probleme gibt es allerdings, wenn Sie die eingelese-
nen Strings in eine Datei schreiben oder in eine grafische Benutzeroberfläche ein-
bauen. Dann sollten Sie das Scanner-Objekt auf der Basis des internen Readers des
Console-Objekts erzeugen:
Scanner sc = new Scanner(System.console().reader());
Die Ausgabe auf die Konsole muss dann ebenfalls über das Console-Objekt erfolgen.
216 >> Passwörter über die Konsole (Standardeingabe) lesen
import java.io.*;
char passwort[];
if (PASSWORT.equals(new String(passwort)))
cons.printf(" %s, Sie sind angemeldet! \n", name);
else
cons.printf(" Anmeldung fehlgeschlagen! \n");
}
}
Ausgabe:
Benutzername eingeben: Dirk
Passwort eingeben:
Dirk, Sie sind angemeldet!
Methode Beschreibung
static setIn(InputStream stream) Setzt System.in auf den Eingabestream stream.
static setErr(PrintStream stream) Setzt System.err auf den Ausgabestream stream.
static setOut(PrintStream stream) Setzt System.out auf den Ausgabestream stream.
import javax.swing.*;
import java.io.*;
/*
* Klasse zur Umleitung der Standardausgabe in eine JTextArea
*/
public class TextAreaPrintStream extends PrintStream {
Ein kleines Problem ist, dass die Basisklasse PrintStream nur Konstruktoren definiert, die ein
File-Objekt, einen Dateinamen oder einen OutputStream als Argument erwarten. Die Klasse
TextAreaPrintStream löst dieses Problem, indem sie den Basisklassenkonstruktor mit dem Out-
putStream-Argument aufruft – allerdings mit einer abgeleiteten OutputStream-Klasse, deren
Konstruktor die Referenz auf die JTextArea-Instanz übergeben werden kann. In dieser Output-
Stream-Klasse wird dann die write()-Methode überschrieben, die die an die Standardausgabe
geschickten Zeichencodes in die JTextArea schreibt.
Zur Erinnerung: Der Konstruktor einer abgeleiteten Klasse ruft als erste Anweisung
H in we i s
immer einen Konstruktor der Basisklasse auf. Ist ein entsprechender super-Aufruf im
Quelltext des Konstruktors nicht vorgesehen, erweitert der Java-Compiler den Kon-
struktorcode automatisch um den Aufruf eines Standardkonstruktors (Konstruktor
ohne Parameter) der Basisklasse.
218 >> Standardein- und -ausgabe umleiten
Das Programm zu diesem Rezept ist ein GUI-Programm, dessen ContentPane mittels einer
JSplitPane-Instanz in zwei Bereiche unterteilt ist:
왘 einem Arbeitsbereich mit zwei JButton-Instanzen, die beim Drücken einen Text an die
Standardausgabe schicken,
왘 einen Logging-Bereich mit der JTextArea-Komponente, in die die Ausgaben umgeleitet
werden.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
Ein- und Ausgabe
public Start() {
// Hauptfenster konfigurieren
setTitle("Umlenken der Standardausgabe");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
abgebrochen werden.
Gut geeignet ist die periodische Ausgabe eines einzelnen Zeichens, beispielsweise eines
Punkts, ohne Leerzeichen oder Zeilenumbrüche:
System.out.print(".");
왘 Die Lebenszeichen sollten nicht zu schnell aufeinander folgen, aber auch nicht zu lange
auf sich warten lassen.
왘 Die Anzahl der Lebenszeichen sollte größer als 4 sein, aber nicht zu groß werden. (Droht
die Konsole mit Lebenszeichen überschwemmt zu werden, so setzen Sie lieber den Zeitab-
stand zwischen den Lebenszeichen herauf.)
왘 Informieren Sie den Anwender vorab, dass nun mit einer längeren Wartezeit zu rechnen
ist.
Nachdem Sie Form und Frequenz der Lebenszeichen ungefähr festgelegt haben, müssen Sie
überlegen, wie Sie für eine periodische Ausgabe der Lebenszeichen sorgen.
// 3. Ergebnis anzeigen
System.out.println("\n Berechnung beendet.\n");
Bei dieser Form entspricht die Anzahl der Lebenszeichen den Durchläufen der Schleife. Wird
die Schleife sehr oft durchlaufen, setzen Sie die Anzahl der Lebenszeichen herab, indem Sie
nur bei jedem zweiten, dritten ... Schleifendurchgang Lebenszeichen ausgeben lassen:
if( i%2)
System.out.print(".");
Wird die Schleife zu selten durchlaufen, müssen Sie mehrere Lebenszeichen über die Schleife
verteilen (eventuell gibt es innere Schleifen, die sich besser zur Ausgabe der Lebenszeichen
eignen).
import java.util.TimerTask;
/**
* TimerTask-Klasse für Konsolen-Fortschrittsanzeige
*/
class ShowProgressTimer extends TimerTask {
Danach wechseln Sie zum Code der Berechnung. Vor dem Start der Berechnung geben Sie eine
Vorankündigung aus, erzeugen ein Timer-Objekt und übergeben diesem eine Instanz der
TimerTask-Klasse, eine anfängliche Verzögerung und die Dauer des Zeitintervalls (in Milli-
sekunden).
Anschließend folgt die eigentliche Berechnung, 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.
Nach Abschluss der Berechnung beenden Sie den Timer.
// Ergebnis zurückliefern
return 42;
}
Ein- und Ausgabe
91 Konsolenmenüs
Bei Menüs denken die meisten Anwender an Menüs von GUI-Anwendungen. Doch auch Kon-
solenanwendungen können einen Leistungsumfang erreichen, der eine menügesteuerte Pro-
grammführung opportun macht.
Die Implementierung eines Konsolenmenüs besteht aus drei Schritten:
1. Anzeigen des Menüs
Die Ausgabe erfolgt zeilenweise mit println()-Aufrufen. Zu jedem Befehl muss ein Code
angegeben werden, über den der Anwender den Befehl auswählen kann. Gut geeignet sind
hierfür Zeichen, Integer-Werte oder Aufzählungskonstanten, da diese in Schritt 3 mittels
einer switch-Verzeigung ausgewertet werden können.
System.console().printf(" Erster Menübefehl <a> \n");
System.console().printf(" Zweiter Menübefehl <b> \n");
...
2. Abfragen der Benutzerauswahl
Der Anwender wird aufgefordert, den Code für einen Befehl einzugeben. Ungültige Einga-
ben, soweit sie nicht in Schritt 3 vom default-Block der switch-Anweisung abgefangen
werden (beispielsweise Strings aus mehr als einem Zeichen), müssen aussortiert werden.
>> Ein- und Ausgabe (IO) 223
Grundstruktur
Soll das Menü nicht nur einmalig zu Beginn des Programms angezeigt werden, gehen Sie so
vor, dass Sie die obigen drei Schritte in eine do-while-Schleife fassen und das Menü um einen
Menübefehl zum Verlassen des Programms erweitern. Wird dieser Menübefehl ausgewählt,
wird die do-while-Schleife und damit das Programm beendet.
import java.util.Scanner;
do {
// 1. Menu anzeigen
System.console().printf("\n");
System.console().printf(" ******************************** \n");
System.console().printf(" Menü \n");
System.console().printf("\n");
System.console().printf(" Erster Menübefehl <a> \n");
System.console().printf(" Zweiter Menübefehl <b> \n");
System.console().printf(" Dritter Menübefehl <c> \n");
System.console().printf(" Programm beenden <q> \n");
System.out.print("\n Ihre Eingabe : ");
// 2. Eingabe lesen
option = NO_OPTION;
input = scan.nextLine();
if(input.length() == 1) // einzelnes Zeichen in Eingabe
option = input.charAt(0);
System.out.println("\n");
// 3. Menübefehl abarbeiten
switch(option) {
case 'a': System.console().printf(" Menübefehl a \n");
break;
case 'b': System.console().printf(" Menübefehl b \n");
break;
case 'c': System.console().printf(" Menübefehl c \n");
break;
case 'q': System.console().printf(" Programm wird beendet \n");
break;
default: System.console().printf(" Falsche Eingabe \n");
}
Ein- und Ausgabe
System.out.println("\n");
Schritt 2 liest eine Eingabe von der Tastatur ein. Besteht die Eingabe aus einem einzelnen Zeichen,
wird sie in option gespeichert und so an die switch-Anweisung weitergegeben. Hat der Anwender
aus Versehen mehrere Tasten gedrückt, wird der Standardwert NO_OPTION weitergereicht. (NO_
OPTION wurde zu Beginn der main()-Methode mit einem Zeichen initialisiert, das keinem Menü-
befehl entspricht. NO_OPTION wird daher vom default-Block der switch-Anweisung behandelt.)
(while(option != 'q')), können Sie die Schleife auch mit einer Label-Sprunganweisung
aus der switch-Verzweigung heraus verlassen:
quit: while(true) {
...
switch(option) {
...
case 'q': System.console().printf(" Programm wird beendet \n");
break quit;
default: System.console().printf(" Falsche Eingabe \n");
}
}
>> Ein- und Ausgabe (IO) 225
Die Anweisung option = 'q' ist nötig, damit die do-while(option != 'q') auch bei Ein-
Achtung
gabe von Q beendet wird. Wird die Schleife wie im vorangehenden Absatz beschrieben
mit einer Sprunganweisung verlassen, kann die Anweisung entfallen.
a;Erster Menübefehl
b;Zweiter Menübefehl
c;Dritter Menübefehl
q;Programm beenden
Die Klasse ConsoleMenu erzeugt aus dieser Datei das folgende Menü:
********************************
Menue
Erster Menübefehl...........<a>
Zweiter Menübefehl..........<b>
Dritter Menübefehl..........<c>
Programm beenden.............<q>
Ihre Eingabe : q
Das Füllzeichen zwischen Befehlstitel und -code (hier der Punkt .) wird als Argument an den
Konstruktor von ConsoleMenu übergeben. Die Menüüberschrift und die Eingabeaufforderung
Ein- und Ausgabe
/**
* Klasse zum Aufbau von Konsolenmenüs
*/
public class ConsoleMenu {
// Menübefehle einlesen
while( (line = in.readLine()) != null) {
menu.clear();
in.close();
throw new ParseMenuException("Fehler beim Parsen der " +
" Menuedatei");
}
code = line.charAt(0);
title = line.substring(2);
for(MenuElem e : menu) {
diff = (maxLength + 10) - e.title.length();
char[] pad = new char[diff];
for(int i = 0; i < pad.length; ++i)
228 >> Automatisch generierte Konsolenmenüs
pad[i] = paddChar;
in.close();
}
for(MenuElem e : menu)
System.console().printf(" %s<%s> \n", e.title, e.code);
printPrompt();
}
input = scan.nextLine();
if(input.length() == 1) // einzelnes Zeichen in Eingabe
option = input.charAt(0);
System.out.println("\n");
return option;
}
}
Für die Ausgabe des Menüs müssen Sie lediglich die Methode printMenu() aufrufen. Die Ein-
gabe des Anwenders können Sie selbst einlesen oder bequem mit getUserOption() abfragen,
siehe Listing 102.
char option;
ConsoleMenu menu;
System.out.println();
try {
menu = new ConsoleMenu("Menu.txt", '.');
quit: while(true) {
menu.printMenu();
option = menu.getUserOption();
System.out.println("\n");
} catch(Exception e) {
System.err.println("Fehler: " + e.getMessage());
}
}
}
94 Kommandozeilenargumente auswerten
Konsolenanwendungen besitzen die erfreuliche Eigenschaft, dass man ihnen beim Aufruf
Argumente mitgeben kann – beispielsweise Optionen, die das Verhalten des Programms steu-
ern, oder zu verarbeitende Daten. Der Java-Interpreter übergibt diese Argumente beim Pro-
grammstart an den args-Array-Parameter der main()-Methode. In der main()-Methode können
Sie die im Array gespeicherten Kommandozeilenargumente abfragen und auswerten.
Das Programm zu diesem Rezept erwartet auf der Kommandozeile drei Argumente: eine Zahl,
ein Operatorsymbol und eine zweite Zahl. Es prüft vorab, ob der Anwender beim Aufruf die
korrekte Anzahl Argumente übergeben hat.
>> Ein- und Ausgabe (IO) 231
Bei einer abweichenden Anzahl weist das Programm den Anwender auf die korrekte Aufruf-
syntax hin und beendet sich selbst.
Stimmt die Anzahl der Argumente, wandelt das Programm die Argumente in die passenden
Datentypen um (Kommandozeilenargumente sind immer Strings) und berechnet, sofern die
Typumwandlung nicht zur Auslösung einer NumberFormatException geführt hat, die gewünschte
Operation.
try {
// Befehl bearbeiten
switch(operator) {
case '+': System.out.println(" = " + (zahl1 + zahl2));
break;
case '-': System.out.println(" = " + (zahl1 - zahl2));
break;
case 'X':
case 'x':
case '*': System.out.println(" = " + (zahl1 * zahl2));
break;
case ':':
case '/': System.out.println(" = " + (zahl1 / zahl2));
break;
default: System.out.println("Operator nicht bekannt");
break;
}
} catch (NumberFormatException e) {
System.err.println(" Ungueltiges Argument");
}
}
}
Auf der Windows-Konsole führen Aufrufe mit * möglicherweise dazu, dass der
H i n we i s
import java.io.*;
class FileUtil {
/**
* Erzeugt eine neue leere Datei; Pfad wird ggf. erzeugt
*
* @param name relativer oder absoluter Dateiname
* @return true bei Erfolg, sonst false (Datei existert schon
* oder keine Schreibrechte)
*/
public static boolean createNewFile(String name) {
try {
// zuerst mal so probieren
result = f.createNewFile();
} catch(Exception e) {
result = false;
}
try {
if(result == false) {
// sicherstellen, dass Pfad existiert und nochmal probieren
int pos = name.lastIndexOf(File.separatorChar);
if(pos >= 0) {
String path = name.substring(0,pos);
File p = new File(path);
result = p.mkdirs();
if(result)
result = f.createNewFile();
}
}
} catch(Exception e) {
e.printStackTrace();
result = false;
}
return result;
}
}
Listing 104: Methode zum sicheren Anlegen neuer Dateien (aus FileUtil.java)
234 >> Datei- und Verzeichniseigenschaften abfragen
if(result)
System.out.println("Leere Datei angelegt!");
else
System.out.println("Datei konnte nicht erzeugt werden!");
}
Ein- und Ausgabe
import java.io.*;
import java.util.Date;
/**
* Klasse zur Ermittlung von Datei-/Verzeichniseigenschaften
*/
class FileInfo {
private String fileName;
private File file;
try {
file = new File(name);
} catch(Exception e) {
e.printStackTrace();
}
}
} catch(Exception e) {
e.printStackTrace();
return false;
}
}
// Liefert den vollen Dateinamen inkl. Pfad oder null bei Fehler
public String getAbsoluteName() {
try {
return file.getCanonicalPath();
} catch(Exception e) {
e.printStackTrace();
if(file != null)
result = file.length();
return result;
}
for(File f : roots) {
String path = f.getCanonicalPath();
if(getAbsoluteName().startsWith(path))
return f;
else
continue;
}
} catch(Exception e) {
e.printStackTrace();
}
return null;
}
} else
return null;
}
// Liefert das Datum der letzten Änderung oder null bei Fehler
public Date getLastModified() {
if(file != null) {
long time = file.lastModified();
return (new Date(time));
} else
return null;
}
}
import java.io.*;
try {
// eine Datei im Standard-Temp Verzeichnis erzeugen
File tmp1 = File.createTempFile("daten_",".txt");
tmp1.deleteOnExit();
// Dateien verwenden
System.out.println(tmp1.getCanonicalPath());
System.out.println(tmp2.getCanonicalPath());
} catch(Exception e) {
e.printStackTrace();
}
}
}
98 Verzeichnisinhalt auflisten
Ein häufiges Problem ist das Durchlaufen aller Dateien und Unterverzeichnisse von einem
gegebenen Wurzelverzeichnis aus. Hierfür eignet sich ein rekursiver Ansatz, bei dem eine
Ein- und Ausgabe
Methode listAllFiles() als Parameter ein Verzeichnis erhält, alle darin enthaltenen Dateien
und Verzeichnisse auflistet und diese dann der Reihe nach durchgeht. Bei einer Datei wird der
Name gespeichert, bei einem Verzeichnis ruft sich die Methode selbst mit diesem Verzeichnis
als Argument auf.
import java.io.*;
import java.util.*;
class FileUtil {
/**
* Auflistung aller Dateien/Verzeichnisse in einem Startverzeichnis
* und in allen Unterzeichnissen
*
* @param rootDir File-Objekt des Startverzeichnisses
* @param includeDirNames Flag, ob auch reine Verzeichnisse als separater
* Eintrag erscheinen (true/false)
* @return ArrayList<File> mit allen File-Objekten
*/
public static ArrayList<File> listAllFiles(File rootDir,
boolean includeDirNames) {
ArrayList<File> result = new ArrayList<File>();
try {
File[] fileList = rootDir.listFiles();
result.addAll(listAllFiles(fileList[i],includeDirNames));
}
else
result.add(fileList[i]);
}
} catch(Exception e) {
e.printStackTrace();
}
return result;
}
}
try {
for(File f : files)
System.out.println(f.getCanonicalPath());
} catch(Exception e) {
e.printStackTrace();
}
}
}
if(st == true)
System.out.println("Datei geloescht");
Voraussetzung für ein erfolgreiches Löschen ist eine Schreibberechtigung auf die gewünschte
Datei für den Benutzer, unter dessen Kennung das Java-Programm ausgeführt wird. Bei Ver-
zeichnissen kommt eine weitere, 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
240 >> Dateien und Verzeichnisse löschen
sorgen, dass alle enthaltenen Dateien und Unterverzeichnisse gelöscht worden sind. Hierzu
kann man den rekursiven Ansatz aus Rezept 98 einsetzen:
import java.io.*;
class FileUtil {
if(st == false)
statusRecursive = false;
}
} catch(Exception e) {
e.printStackTrace();
statusRecursive = false;
}
}
// Datei/Verzeichnis löschen
boolean status = startFile.delete();
if(result)
System.out.println("Datei/Verzeichnis geloescht");
else
System.out.println("Konnte nicht loeschen!");
}
}
import java.util.*;
import java.io.*;
import java.nio.channels.*;
class FileCopy {
/**
* Ausgabe aller Datei-/Verzeichnisnamen in einem Startverzeichnis und in
* allen Unterzeichnissen
*
* @param rootDir File-Objekt des Startverzeichnisses
* @param includeDirNames Flag, ob auch Verzeichnisnamen als separater
* Eintrag erscheinen (true/false)
* @return ArrayList<File> mit allen File-Objekten
*/
public static ArrayList<File> listAllFiles(File rootDir,
boolean includeDirNames) {
ArrayList<File> result = new ArrayList<File>();
try {
File[] fileList = rootDir.listFiles();
if(fileList[i].isDirectory() == true) {
if(includeDirNames)
result.add(fileList[i]);
result.addAll(listAllFiles(fileList[i],includeDirNames));
}
else
result.add(fileList[i]);
}
} catch(Exception e) {
e.printStackTrace();
}
Ein- und Ausgabe
return result;
}
/**
* Kopieren einer Datei/Verzeichnisses; eine vorhandene Zieldatei
* wird überschrieben
*
* @param sourceDir Name des zu kopierenden Verzeichnisses
* @param targetRoot Name des Zielverzeichnisses, in das hineinkopiert
* werden soll (muss existieren)
* @return true bei Erfolg, ansonsten false
*/
public static boolean copyTree(String sourceDir, String targetRoot) {
boolean result;
try {
File source = new File(sourceDir);
File root = new File(targetRoot);
if(target.exists() == false) {
boolean st = target.mkdir();
if(st == false)
return false;
}
Listing 113: Methoden zum Kopieren von Dateien und Verzeichnissen (Forts.)
>> Ein- und Ausgabe (IO) 243
for(File f : fileNames) {
String fullName = f.getCanonicalPath();
int pos = fullName.indexOf(sourceDir);
String subName = fullName.substring(pos + sourceDir.length()+1);
String targetName = targetRootName + subName;
if(f.isDirectory()) {
// Unterverzeichnis ggf. anlegen
File t = new File(targetName);
if(st == false)
result = false;
}
continue;
}
if(st == false)
result = false;
}
} catch(Exception e) {
e.printStackTrace();
result = false;
}
return result;
}
/**
* Kopieren einer Datei; 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 targetFile) {
boolean result;
try {
// Eingabedatei öffnen
Listing 113: Methoden zum Kopieren von Dateien und Verzeichnissen (Forts.)
244 >> Dateien und Verzeichnisse kopieren
// Zieldatei öffnen
FileOutputStream outputFile = new FileOutputStream(targetFile);
FileChannel output= outputFile.getChannel();
// kopieren
input.transferTo(0,num,output);
Ein- und Ausgabe
input.close();
output.close();
result = true;
} catch(Exception e) {
e.printStackTrace();
result = false;
}
return result;
}
}
Listing 113: Methoden zum Kopieren von Dateien und Verzeichnissen (Forts.)
Das Start-Programm demonstriert den Aufruf. Denken Sie daran, Quell- und Zielverzeichnis
vor dem Aufruf anzulegen (bzw. die Pfade für sourceDir und targetRootDir anzupassen).
if(result)
System.out.println("Verzeichnis kopiert!");
else
System.out.println("Fehler beim Kopieren!");
}
}
import java.io.*;
try {
} catch(Exception e) {
e.printStackTrace();
}
}
}
/**
Listing 116: Methoden zum Lesen und Schreiben von Textdateien beliebiger Zeichenkodierung
*
* @author Peter Müller
*/
import java.util.*;
import java.io.*;
class FileUtil {
Ein- und Ausgabe
/**
* Lädt eine Textdatei mit der angegebenen Zeichenkodierung
*
* @param fileName Name der Datei
* @param charSet Name der Zeichenkodierung, z.B. UTF-8 oder ISO-8859-1;
* 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, String charSet) {
String result = null;
try {
InputStreamReader reader;
if(charSet != null)
reader = new InputStreamReader(new FileInputStream(fileName),
charSet);
else
reader = new InputStreamReader(new FileInputStream(fileName));
in.close();
return buffer.toString();
} catch(Exception e) {
e.printStackTrace();
result = null;
}
return result;
}
>> Ein- und Ausgabe (IO) 247
/**
* Schreibt einen String als Textdatei in der angegebenen Zeichenkodierung.
*
* @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, String fileName,
String charSet) {
boolean result = true;
if(charSet != null)
writer = new OutputStreamWriter(new FileOutputStream(fileName),
charSet);
else
writer = new OutputStreamWriter(new FileOutputStream(fileName));
} catch(Exception e) {
e.printStackTrace();
result = false;
}
return result;
}
}
Listing 116: Methoden zum Lesen und Schreiben von Textdateien beliebiger Zeichenkodierung
(Forts.)
}
}
return str;
}
int countBytes = 0;
char[] bytesRead = new char[512];
return str.toString();
}
>> Ein- und Ausgabe (IO) 249
Um das Lesen des Dateiinhalts möglichst effizient zu gestalten, werden die Zeichen nicht ein-
zeln mit read(), sondern in 512-Byteblöcken mit read(char[]) eingelesen. Außerdem werden
die Zeichen nicht direkt an ein String-Objekt angehängt (etwa mit + oder concat()), sondern in
einem StringBuffer gespeichert. Der Grund ist Ihnen sicherlich bekannt: Strings sind in Java
immutable (unveränderlich), d.h., 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. StringBuffer- und StringBuilder-Objekte sind dagegen mutable
(veränderlich) und werden direkt bearbeitet.
StringBuffer und StringBuilder sind nahezu wie Zwillinge, nur dass StringBuilder schneller
in der Ausführung ist, weil nicht threadsicher. Da gleichzeitige Zugriffe aus verschiedenen
Threads auf die lokale Variable str nicht gegeben sind, haben wir für die obige Implementie-
rung StringBuilder gewählt.
import java.io.*;
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;
try {
BufferedInputStream input;
input = new BufferedInputStream(new FileInputStream(fileName));
int num = input.available();
result = new byte[num];
input.read(result, 0, num);
input.close();
} catch(Exception e) {
e.printStackTrace();
result = null;
}
return result;
}
/**
try {
BufferedOutputStream output;
output = new BufferedOutputStream(new FileOutputStream(fileName));
Ein- und Ausgabe
output.write(data, 0, data.length);
output.close();
} catch(Exception e) {
e.printStackTrace();
result = false;
}
return result;
}
}
Listing 118: Methoden zum Lesen und Schreiben von Binärdateien (Forts.)
import java.io.*;
/**
/**
* Datei für wahlfreien Zugriff öffnen
*
* @param mode Modus "r" = lesen,
* "rw" = lesen und schreiben
* @return true bei Erfolg, sonst false
*/
public boolean open(String mode) {
boolean result = true;
try {
file = new RandomAccessFile(fileName, mode);
} catch(Exception e) {
e.printStackTrace();
result = false;
}
return result;
}
/**
* Datei schließen
Listing 120: RandomAccess.java – eine Klasse für den Zugriff auf beliebige Positionen in einer
Textdatei
3. RandomAccessFile hat zwar auch eine Methode readUTF(), die leider ein java-spezifisches UTF-Format erwartet,
das nicht UTF-8-kompatibel ist, und somit in der Regel nicht brauchbar ist.
252 >> Random Access (wahlfreier Zugriff)
*
* @return true bei Erfolg, sonst false
*/
public boolean close() {
try {
file.close();
return true;
} catch(Exception e) {
e.printStackTrace();
return false;
}
}
Ein- und Ausgabe
/**
* Liefert die aktuelle Größe der Datei in Bytes oder -1 bei Fehler
*
* @return Anzahl Bytes
*/
public long getLength() {
try {
return file.length();
} catch(Exception e) {
e.printStackTrace();
return -1;
}
}
/**
* Liefert den aktuellen Wert des Dateizeigers
*
* @return long-Wert mit Position oder -1 bei Fehler
*/
public long getFilePointer() {
try {
return file.getFilePointer();
} catch(Exception e) {
e.printStackTrace();
return -1;
}
}
/**
* Hängt die übergebenen Bytes an das Ende der Datei
*/
public void append(byte[] data) {
Listing 120: RandomAccess.java – eine Klasse für den Zugriff auf beliebige Positionen in einer
Textdatei (Forts.)
>> Ein- und Ausgabe (IO) 253
try {
file.seek(file.length());
file.write(data);
} catch(Exception e) {
e.printStackTrace();
}
}
/**
* Hängt den String in der gewünschten Kodierung an
*/
} catch(Exception e) {
e.printStackTrace();
}
}
/**
* Liest die angegebene Anzahl Bytes ein und liefert sie als Array zurück
*
* @param startPos Position, ab der gelesen werden soll
* @param num Anzahl einzulesender Bytes
* @return byte[] mit eingelesenen Daten oder null bei Fehler
*/
public byte[] read(long startPos, int num) {
try {
file.seek(startPos);
byte[] data = new byte[num];
int actual = file.read(data, 0, num);
data = tmp;
}
return data;
Listing 120: RandomAccess.java – eine Klasse für den Zugriff auf beliebige Positionen in einer
Textdatei (Forts.)
254 >> Random Access (wahlfreier Zugriff)
} catch(Exception e) {
e.printStackTrace();
return null;
}
/**
* Schreibt die übergebenen Bytes in die Datei
*
* @param data Position, ab der geschrieben werden soll
* @param startPos Array mit zu schreibenden Daten
Ein- und Ausgabe
} catch(Exception e) {
e.printStackTrace();
return false;
}
/**
* Schreibt den übergebenen String in der gewünschten Zeichenkodierung
* als Bytefolge
*
* @param data zu schreibender String
* @param encoding Name der Zeichenkodierung
* @param startPos Startposition, ab der geschrieben werden soll
* @return true bei Erfolg, sonst false
*/
pu