Titel   Inhalt   Suchen   Index   DOC  Handbuch der Java-Programmierung, 7. Auflage
 <<    <     >    >>   API  Kapitel 17 - Utility-Klassen I

17.3 Die Klasse System



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.

17.3.1 System-Properties

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
Listing 17.4: Ausgeben der System-Properties

17.3.2 in, err und out

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.

 Hinweis 

17.3.3 exit

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.

 Warnung 

17.3.4 gc

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.

17.3.5 currentTimeMillis

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.

 Hinweis 

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
Listing 17.5: Die Auflösung des System-Timers bestimmen

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.

 Hinweis 

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:

 Tip 

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
Listing 17.6: Die Auflösung von Thread.sleep bestimmen

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 %)

17.3.6 arraycopy

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
Listing 17.7: Verwendung von System.arraycopy

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