Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 7. Auflage |
<< | < | > | >> | API | Kapitel 17 - Utility-Klassen I |
Neben den bisher vorgestellten Datenstrukturen des Pakets java.util gibt es in java.lang eine Klasse System, die eine Reihe nützlicher Hilfsmittel zur Verfügung stellt. Die wichtigsten von ihnen sollen in den folgenden Abschnitten besprochen werden.
In Java gibt es keine Möglichkeit, direkt auf die Umgebungsvariablen eines Programms zuzugreifen. Ein solcher Zugriff wurde von den Java-Designern als nichtportabel angesehen und stattdessen durch das Konzept der Properties ersetzt. Properties sind Listen von Eigenschaften, die dem Programm vom Java-Laufzeitsystem zur Verfügung gestellt werden. Jede Eigenschaft besitzt einen Namen, unter dem auf sie zugegriffen werden kann. Das Java-Laufzeitsystem stellt standardmäßig die folgenden Properties zur Verfügung:
Property | Bedeutung |
java.version | Java-Versionsnummer |
java.vendor | Herstellerspezifische Zeichenkette |
java.vendor.url | URL (also ein Internet-Link) zum Hersteller |
java.home | Installationsverzeichnis |
java.class.version | Versionsnummer der Java-Klassenbibliothek |
java.class.path | Aktueller Klassenpfad |
os.name | Name des Betriebssystems |
os.arch | Betriebssystem-Architektur |
os.version | Versionsnummer des Betriebssystems |
file.separator | Trennzeichen für die Bestandteile eines Pfadnamens |
file.encoding | Default-Zeichensatz der Virtual Machine |
path.separator | Trennzeichen für die Laufwerksangabe eines Pfadnamens |
line.separator | Zeichenkette für Zeilenschaltung |
user.name | Name des angemeldeten Benutzers |
user.home | Home-Verzeichnis |
user.dir | Aktuelles Arbeitsverzeichnis |
java.vm.specification.version | Version der VM-Spezifikation |
java.vm.specification.vendor | Hersteller der VM-Spezifikation |
java.vm.specification.name | Bezeichnung der VM-Spezifikation |
java.vm.version | VM-Version |
java.vm.vendor | Hersteller der VM |
java.vm.name | Name der VM-Implementierung |
java.specification.version | Version der Spezifikation der Laufzeitumgebung |
java.specification.vendor | Hersteller der Spezifikation der Laufzeitumgebung |
java.specification.name | Bezeichnung der Spezifikation der Laufzeitumgebung |
java.io.tmpdir | Pfad zum Verzeichnis für temporäre Dateien auf diesem System |
Tabelle 17.2: Standard-Properties
Für den Zugriff auf diese Eigenschaften steht die Klasse Properties aus dem Paket java.util zur Verfügung. Sie bietet die Möglichkeit, Property-Listen zu erzeugen, mit Werten zu füllen und vorhandene Werte auszulesen. Die Klasse Properties ist eine Ableitung der Klasse Hashtable und stellt damit eine Tabelle von Schlüssel-/Wertepaaren dar.
Für den Zugriff auf einzelne Properties reicht meist die einfach zu bedienende Klassenmethode getProperty der Klasse System in java.lang aus:
public static String getProperty(String key) public static String getProperty(String key, String def) |
java.lang.System |
Die erste Variante liefert die Eigenschaft mit dem Namen key in Form einer Zeichenkette. Falls keine Eigenschaft mit diesem Namen gefunden wurde, wird null zurückgegeben. Die zweite Variante erlaubt die Übergabe eines Standardwerts. Der Unterschied zur ersten Variante besteht darin, dass nicht null, sondern der Standardwert zurückgegeben wird, wenn die gesuchte Eigenschaft nicht gefunden wurde.
Die Methode getProperties liefert das komplette Properties-Objekt mit den System-Properties:
public static Properties getProperties() |
java.lang.System |
Das folgende Programm gibt eine Liste aller System-Properties auf dem Bildschirm aus. Es verwendet dazu zunächst die Methode getProperties, um das System-Properties-Objekt zu beschaffen. Anschließend erzeugt es durch Aufruf von propertyNames einen Enumerator, mit dem alle Schlüsselwerte durchlaufen werden können und mit dem durch Aufruf von getProperty der zugehörige Wert ermittelt werden kann. Auf diese Weise listet das Programm alle verfügbaren System-Properties auf (das sind in der Regel sehr viel mehr als die plattformübergreifend spezifizierten):
001 /* Listing1704.java */ 002 003 import java.util.*; 004 005 public class Listing1704 006 { 007 public static void main(String[] args) 008 { 009 Properties sysprops = System.getProperties(); 010 Enumeration<?> propnames = sysprops.propertyNames(); 011 while (propnames.hasMoreElements()) { 012 String propname = (String)propnames.nextElement(); 013 System.out.println( 014 propname + "=" + System.getProperty(propname) 015 ); 016 } 017 } 018 } |
Listing1704.java |
In den vorangegangenen Kapiteln wurde schon häufig der Aufruf System.out.println verwendet, um Daten auf die Standardausgabe bzw. in ein Debug-Fenster auszugeben. Diese Anweisung ruft die Methode println des Objekts out der Klasse System auf. Dabei ist out eine statische Variable vom Typ PrintStream, die beim Starten des Programms so initialisiert wird, dass ihre Ausgabe auf die Standardausgabe geleitet wird.
Analog zu out gibt es die statischen Variablen err und in. Dabei dient err zur Ausgabe von Fehlermeldungen und in ist ein Standardeingabekanal, der dazu verwendet werden kann, Eingaben von der Tastatur zu lesen.
Mit Hilfe der Methoden setIn, setOut und setErr ist es sogar möglich, die Standardein- und -ausgabe aus dem Programm heraus umzuleiten:
public static void setIn(InputStream in) public static void setOut(PrintStream out) public static void setErr(PrintStream err) |
java.lang.System |
Die Verwendung dieser Klassenvariablen ist im Grunde genommen nicht konform mit dem Dialogkonzept einer GUI-Anwendung. Ihr Einsatz kann aber immer dann sinnvoll sein, wenn Java-Programme geschrieben werden sollen, die keine ausgefeilte Oberfläche benötigen. In diesem Fall sind sie ein nützliches Hilfsmittel, um einfache Ein-/Ausgaben ohne großen Aufwand realisieren zu können. Bereits in Kapitel 4 wurde gezeigt, wie man diese Routinen zur Ein- und Ausgabe verwenden kann. Weitere Informationen darüber sind in Kapitel 20 zu finden, das sich mit Byte-Streams beschäftigt. |
|
Auch Aufrufe der Methode System.exit sind uns schon begegnet:
public static void exit(int status) |
java.lang.System |
Mit System.exit wird das laufende Programm beendet. Der Aufrufparameter status dient als Fehleranzeige und wird als Exitcode an den Aufrufer des Programms zurückgegeben. Gemäß Konvention zeigt dabei ein Wert größer oder gleich 1 einen Fehler während der Programmausführung an, während 0 ein fehlerfreies Programmende signalisiert.
Neben der Methode System.exit bietet die weiter unten beschriebene Klasse Runtime auch die Methode System.halt an, bei der die Virtual Machine sofort angehalten wird. Im Unterschied zu Runtime.halt werden dabei weder finalize -Methoden noch finally-Zweige abgearbeitet. Dies kann z.B. beim Zugriff auf eine Datenbank dazu führen, dass externe Ressourcen nicht zurückgegeben werden. Sie sollten deshalb auf die Verwendung von Runtime.halt verzichten. |
|
public static void gc() |
java.lang.System |
Ein Aufruf der Methode gc führt einen expliziten Aufruf des Garbage Collectors durch. Dieser sucht dann nach freiem Speicher und gibt diesen an das Laufzeitsystem zurück. Normalerweise ist ein Aufruf dieser Methode nicht erforderlich, denn der Garbage Collector läuft ständig als niedrig priorisierter Thread im Hintergrund. Der Aufruf von gc ist immer dann sinnvoll, wenn eine explizite Kontrolle über den Zeitpunkt der Speicherfreigabe gewünscht ist.
Die Methode currentTimeMillis liefert die Anzahl der Millisekunden, die zum Zeitpunkt des Aufrufs seit Mitternacht des 1.1.1970 vergangen sind:
public static long currentTimeMillis() |
java.lang.System |
Ob dabei tatsächlich eine Auflösung von einer Millisekunde erreicht wird, ist z.B. von dem Betriebssystem und der konkreten Java-Implementierung abhängig. In PC-basierten Java-Systemen orientiert sie sich meist an der Auflösung des System-Timers. |
|
Mit dem folgenden Beispielprogramm kann die Auflösung des System-Timers ermittelt werden:
001 /* Listing1705.java */ 002 003 public class Listing1705 004 { 005 public static void main(String[] args) 006 { 007 long t1, t2; 008 int actres, sumres = 0, i = 0; 009 while (true) { 010 ++i; 011 t1 = System.currentTimeMillis(); 012 while (true) { 013 t2 = System.currentTimeMillis(); 014 if (t2 != t1) { 015 actres = (int)(t2 - t1); 016 break; 017 } 018 } 019 sumres += actres; 020 System.out.print("it="+i+", "); 021 System.out.print("actres="+actres+" msec., "); 022 System.out.print("avgres="+(sumres/i)+" msec."); 023 System.out.println(""); 024 try { 025 Thread.sleep(500); 026 } catch (InterruptedException e) { 027 //nichts 028 } 029 } 030 } 031 } |
Listing1705.java |
Das Programm bestimmt zunächst die aktuelle Systemzeit und merkt sich den Wert in der Variablen t1. Nun wird die Systemzeit in einer Schleife erneut so oft gemessen, bis sie sich geändert hat. Die Differenz zwischen beiden Werten wird als Auflösung des aktuellen Durchgangs angesehen und der Variablen actres zugewiesen.
Um eine größere Genauigkeit zu erzielen, führt das Programm die Bestimmung der Auflösung mit Hilfe der äußeren Schleife viele Male durch. Die dabei jeweils ermittelte Auflösung wird in der Variablen sumres addiert und durch Division durch die Anzahl der Schleifendurchläufe zur Ermittlung des gleitenden Durchschnitts verwendet. |
|
Das so errechnete Ergebnis pendelt sich bereits nach wenigen Durchläufen
unter Windows 7 auf einen Wert von 15 ms. ein:
it=1, actres=16 msec., avgres=16 msec.
it=2, actres=15 msec., avgres=15 msec.
it=3, actres=16 msec., avgres=15 msec.
it=4, actres=15 msec., avgres=15 msec.
...
it=23, actres=16 msec., avgres=15 msec.
it=24, actres=15 msec., avgres=15 msec.
it=25, actres=16 msec., avgres=15 msec.
it=26, actres=15 msec., avgres=15 msec.
Tatsächlich erreichen wir auf einer anderen Testmaschine unter
Ubuntu 10 eine Auflösung von ca. 1 ms.
it=1, actres=1 msec., avgres=1 msec.
it=2, actres=1 msec., avgres=1 msec.
it=3, actres=1 msec., avgres=1 msec.
it=4, actres=1 msec., avgres=1 msec.
...
it=61, actres=1 msec., avgres=1 msec.
it=62, actres=1 msec., avgres=1 msec.
it=63, actres=1 msec., avgres=1 msec.
it=64, actres=1 msec., avgres=1 msec.
Interessanterweise bietet die Methode sleep der Klasse Thread (sie wird in Abschnitt 23.2.3 beschrieben) auf aktuellen Oracle-JDKs unter Windows mit etwa 1 ms. eine wesentlich höhere Auflösung als currentTimeMillis. Diese Eigenschaft ist allerdings nicht dokumentiert und kann von Interpreter zu Interpreter sehr unterschiedlich sein. Selbst auf ein und derselben Java-Maschine kann es durch unterschiedliche Lastsituationen zu Abweichungen kommen. Das folgende Programm ermittelt die Auflösung von sleep: |
|
001 /* Listing1706.java */ 002 003 public class Listing1706 004 { 005 public static long testSleep(int millis) 006 { 007 final int MINDURATION = 3000; 008 int cnt = (millis >= MINDURATION ? 1 : MINDURATION/millis); 009 long start = System.currentTimeMillis(); 010 for (int i = 0; i < cnt; ++i) { 011 try { 012 Thread.sleep(millis); 013 } catch (InterruptedException e) { 014 } 015 } 016 long end = System.currentTimeMillis(); 017 return (end - start) / cnt; 018 } 019 020 public static void main(String[] args) 021 { 022 final int DATA[] = {345, 27, 1, 1962, 2, 8111, 6, 89, 864}; 023 for (int i = 0; i < DATA.length; ++i) { 024 System.out.println("Aufruf von sleep(" + DATA[i] + ")"); 025 long result = testSleep(DATA[i]); 026 System.out.print(" Ergebnis: " + result); 027 double prec = ((double)result / DATA[i] - 1.0) * 100; 028 System.out.println(" (" + (prec > 0 ? "+": "") + prec + " %)"); 029 } 030 } 031 } |
Listing1706.java |
Ein Aufruf unter dem JDK 1.7 auf einem Ubuntu-System im Leerlauf ergab
folgendes Ergebnis:
Aufruf von sleep(345)
Ergebnis: 345 (0.0 %)
Aufruf von sleep(27)
Ergebnis: 27 (0.0 %)
Aufruf von sleep(1)
Ergebnis: 1 (0.0 %)
Aufruf von sleep(1962)
Ergebnis: 1962 (0.0 %)
Aufruf von sleep(2)
Ergebnis: 2 (0.0 %)
Aufruf von sleep(8111)
Ergebnis: 8111 (0.0 %)
Aufruf von sleep(6)
Ergebnis: 6 (0.0 %)
Aufruf von sleep(89)
Ergebnis: 89 (0.0 %)
Aufruf von sleep(864)
Ergebnis: 864 (0.0 %)
Der gleiche Lauf unter Windows 7 unter Java 6 erzeugt folgendes Ergebnis:
Aufruf von sleep(345)
Ergebnis: 345 (0.0 %)
Aufruf von sleep(27)
Ergebnis: 27 (0.0 %)
Aufruf von sleep(1)
Ergebnis: 1 (0.0 %)
Aufruf von sleep(1962)
Ergebnis: 1962 (0.0 %)
Aufruf von sleep(2)
Ergebnis: 2 (0.0 %)
Aufruf von sleep(8111)
Ergebnis: 8112 (+0.012328936012817593 %)
Aufruf von sleep(6)
Ergebnis: 6 (0.0 %)
Aufruf von sleep(89)
Ergebnis: 89 (0.0 %)
Aufruf von sleep(864)
Ergebnis: 864 (0.0 %)
Als letzte Methode der Klasse System soll arraycopy vorgestellt werden:
public static native void arraycopy( Object src, int src_position, Object dst, int dst_position, int length ) |
java.lang.System |
arraycopy kann dazu verwendet werden, Arrays oder Teile davon zu kopieren. Dabei können die Elemente sowohl innerhalb desselben Arrays als auch in ein anderes Array kopiert werden. Falls innerhalb desselben Arrays kopiert wird, dürfen sich Quell- und Zielbereich auch überlappen. Die Methode arbeitet sowohl mit elementaren als auch mit Objekttypen, Ziel- und Quellarray müssen lediglich zuweisungskompatibel sein. Da die Methode in C bzw. Assembler implementiert ist, arbeitet sie recht performant.
Als erste Argumente werden das Quellarray src und die Startposition src_position angegeben. Anschließend folgen das Zielarray dst und die Zielposition dst_position. Als letztes Argument wird die Länge length des zu kopierenden Bereichs angegeben. Falls die Argumente auf Elemente zeigen, die außerhalb des Arrays liegen, wird eine Ausnahme des Typs ArrayIndexOutOfBoundsException ausgelöst. Falls ein Element aufgrund eines Typfehlers nicht gespeichert werden kann, gibt es eine Ausnahme des Typs ArrayStoreException.
Das folgende Programm zeigt die Verwendung von arraycopy an einem einfachen Beispiel:
001 /* Listing1707.java */ 002 003 public class Listing1707 004 { 005 public static void main(String[] args) 006 { 007 int[] ar = {0,0,0,0,0,0,0,0,0,0}; 008 009 for (int i = 0; i < 10; ++i) { 010 System.arraycopy(ar,0,ar,1,9); 011 ar[0] = i; 012 } 013 System.out.print("ar = "); 014 for (int i = 0; i < 10; ++i) { 015 System.out.print(ar[i] + " "); 016 } 017 System.out.println(""); 018 } 019 } |
Listing1707.java |
Das Programm füllt ein zehnelementiges Array von Ganzzahlen,
das zunächst nur Nullen enthält, mit den Zahlen 0 bis 9.
Dabei wird jeweils durch Kopieren der ersten neun Elemente an die
zweite Position des Arrays an der ersten Position Platz gemacht, um
dort den Inhalt des fortlaufenden Schleifenzählers abzulegen.
Nach zehn Durchläufen stehen somit die Zahlen 0 bis 9 verkehrt
herum im Array. Die Ausgabe des Programms ist:
ar = 9 8 7 6 5 4 3 2 1 0
Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 7. Auflage, Addison Wesley, Version 7.0 |
<< | < | > | >> | API | © 1998, 2011 Guido Krüger & Heiko Hansen, http://www.javabuch.de |