Kapitel 10 Package java.lang
Die Java-Sprache ist so eng an das Package java.lang gebunden, dass es implizit importiert wird. Dies gewährleistet, dass so essenzielle Klassen wie Class, Thread und Object immer im Zugriff stehen.
Daneben bietet java.lang noch Wrapper-Klassen, Math-, String- und System-Unterstützung sowie eine generelle Ausnahme-Hierarchie für Error- und Exception-Handling.
In diesem Kapitel werden nur ausgewählte Klassen und Methoden von java.lang unter dem Aspekt »Konzept, Idiome und Einsatz« vorgestellt. Methoden, die ohnehin klar sind, werden ignoriert.
10.1 Object ![down](common/down.gif) ![top](common/15x15leer.gif)
Die Java-Sprache basiert auf einfacher Vererbung. Die Sprach-Designer hatten zwischen Komplexität und Flexibilität der Mehrfachvererbung abzuwägen und haben sich dann für einen Smalltalk-Weg und gegen C++ entschieden.
Object: Basisklasse aller Klassen
Diese Entscheidung führte dann fast zwangsläufig zu Object, der Mutter aller Objekte, von der implizit alle Klassen abgeleitet werden.
Von großer Tragweite ist sicherlich die Frage, welche Methoden Object jeder Klasse automatisch mit auf den Weg geben soll. Bekanntlich sind es elf Instanz-Methoden.
Die Methoden zur Synchronisierung wurden bereits in Kapitel 9, Threads, ausführlich behandelt, finalize() in 5.9, clone() in 6.10.2 und toString() implizit in praktisch jedem print(). Es verbleiben damit nur noch drei Methoden, die allerdings recht interessant sind.
10.1.1 Overriding equals() ![down](common/down.gif) ![top](common/top.gif)
equals(): Identität oder Werte-Semantik
Die Methode equals() testet per Default nur die Identität von Objekten:
public boolean equals(Object obj) {return (this==obj);}
equals(): Vorsicht vor Overloading
Für wertebasierende Vergleiche von Objekten (Werte-Semantik) muss equals() überschrieben werden, d.h. nahezu für jede Klasse.
|
Overriding von equals() muss die o.a. Signatur beachten, sonst ist es Overloading (siehe 5.2). |
![Icon](common/i.gif)
Implementation von equals()
Für eine korrekte und effiziente Implementation von equals() sollte man einem Idiom folgen, wobei zwischen (Basis-)Klassen, deren Superklasse Object ist, und ihren Subklassen unterschieden wird:
class Any /* extends
Base */ {
public boolean equals(Object
o) {
if (o==null) return false;
if (this==o) return true;
equals(): Unterscheidung nach Basis- oder
Subklasse
// 1. Any ist eine Basisklasse (extends Object)
if (getClass() != o.getClass()) return false;
// 2. Any ist eine Subklasse (einer Basisklasse)
if (!super.equals(o)) return false;
// Start des Vergleichs der Any-Instanz-Variablen...
return valuesAreEqual; // true oder false
}
}
Erklärung: Die ersten beiden Zeilen testen Existenz bzw. Identität.
Basisklasse: Ist Any eine Basisklasse, wird zuerst geprüft, ob beide Objekte aus derselben Klasse sind. Da das this-Objekt entweder zu Any bzw. einer Subklasse gehört, ist dies nur dann true, wenn auch o zu Any bzw. derselben Subklasse gehört.
Subklasse: Ist Any Subklasse in einer Hierarchie, müssen die Superklassen zuvor erfolgreich auf Gleichheit testen.
Für beide Fälle erfolgt dann erst der eigentliche Member-Vergleich.
Beispiel
equals()-Muster zur Basisklasse
class Course { // Basisklasse,
also 1. Fall
private String id;
public Course(String id) { this.id=id; }
public String getId() { return id; }
public boolean equals (Object o) {
if (o==null) return false;
if (this==o) return true;
if (getClass() != o.getClass()) return
false;
return id.equals(((Course)o).getId()); // Werte-Vergleich
}
}
equals()-Muster zur Subklasse
Eine von Course abgeleitete Klasse implementiert dann equals() nach der Subklassen-Variante:
class ECourse extends Course {
private int level;
public ECourse(String id, int level) {
super(id); this.level= level;
}
public int getLevel() { return level; }
public boolean equals (Object o) {
if (o==null) return false;
if (this==o) return true;
if (!super.equals(o)) return false;
return level==(((ECourse)o).getLevel()); // Member-Vergleich
}
}
Ein Test liefert das gewünschte Ergebnis:
public class Test {
public static void main(String[] args) {
Course c1= new Course("Java");
ECourse c2= new ECourse("Java", 1);
System.out.println(c1.equals(c2)); // ::
false
System.out.println(c2.equals(c1)); // :: false
System.out.println(
c2.equals(new ECourse("Java", 2))); // :: false
System.out.println(
c2.equals(new ECourse("Java", 1))); // :: true
}
}
10.1.2 getClass() vs. instanceof ![down](common/down.gif) ![top](common/top.gif)
![Icon](common/Kaktus.gif)
Der Unterschied zwischen getClass() und instanceof ist subtil, aber eminent wichtig:
getClass() vs. instanceof: symmetrische vs. asymmetrische Operation
Die Methode equals() muss symmetrisch sein, d.h., es muss gelten:
a.equals(b) == b.equals(a)
Der Operator instanceof ist leider nicht symmetrisch:
a instanceof B /Þ
b instanceof A.
Wer nun z.B. aus persönlicher Antipathie getClass() durch den instanceof-Operator austauscht, wird überrascht sein.
Beispiel
Wir tauschen in der Klasse Course aus Overriding equals() die Methode getClass() gegen instanceof aus:
class Course {
private String id;
public Course(String id) { this.id=id; }
public String getId() { return id; }
public boolean equals (Object o) {
if (o==null) return false;
if (this==o) return true;
if (!(o instanceof Course))return
false; ¨
return id.equals(((Course)o).getId()); ¦
}
}
Nun führen wir denselben Test wie oben in Overriding equals() aus:
instanceof:
unverträglich mit Idiom
public class Test {
public static void main(String[] args) {
Course c1= new Course("Java");
ECourse c2= new ECourse("Java", 1);
System.out.println(c1.equals(c2)); // :: true
System.out.println(c2.equals(c1)); // :: ClassCastException
}
}
Ausgabe: Nicht unbedingt das, was man sich wünscht!
Erklärung: In Zeile ¨ liefert instanceof für alle Subklassen von Course das Ergebnis true. Zeile ¦ liefert dann bei gleichen Feldern der beiden Basisklassen ebenfalls true.
![Icon](common/T.gif)
Der Cast beim Vergleich in der letzten Anweisung von ECourse.equals() führt dann zur Ausnahme.
Symmetrische Methode: keine asymmetrischen Operationen
verwenden
Fazit
|
Symmetrische Methoden sollten keine asymmetrischen Operationen auf ihren Argumenten anwenden. |
10.1.3 Semantik von Gleichheit ![down](common/down.gif) ![top](common/top.gif)
Bedeutung von »gleich« ist domänen-
spezifisch
Im Gegensatz zu »Identität« ist der Begriff »Gleichheit« domänenspezifisch, d.h. nicht generell zu definieren:
|
Gleichheit kann – abhängig von der Anwendung – Identität oder irgendeine Art von Ähnlichkeit bedeuten. |
Zur Definition können der Wert eines Felds, die Werte aller Felder oder rekursiv alle Felder von inneren Objekten herangezogen werden.1
Gleichheit im Idiom zu equals()
equals()-Semantik:
nur für Objekte
Das in 10.1.1 angegebene Template geht von folgender Annahme aus:
»Nur Objekte derselben Klasse können gleich sein.«
Verträgt sich diese Annahme mit der Applikation, ist alles ok.2
Gleichheit und das Interface-Pattern
equals()-Semantik: nicht auf Interface-Ebene
Auch das Interface-Pattern (siehe 6.6) kann equals() bzw. das Idiom tangieren.
Denn werden Objekte als Repräsentanten von Interfaces behandelt, ist es nur ein kleiner Schritt, Gleichheit auf der Ebene von Interfaces einzuführen.
Werden z.B. in einer Applikation Teile bzw. Mitarbeiter nur als Interface-Objekte ITeil bzw. IMitarbeiter im Code verwendet, und sind sie genau dann gleich, wenn ihre Teile- bzw. Mitarbeiter-Nummern übereinstimmen, so folgt daraus:
|
Die Signatur von equals() darf zwar nicht geändert werden, aber das Template in 10.1.1 muss für Interfaces angepasst werden. |
10.1.4 Beziehung zwischen equals() und hashCode() ![down](common/down.gif) ![top](common/top.gif)
hashCode():
Objekt-Identität in Klasse Object
Die Object-Implementation von hashCode() wandelt die Speicher-Adresse in eine Integer um. Damit sind zwei Instanzen o1, o2 der Klasse Object genau dann identisch, wenn ihre Hashcodes übereinstimmen:
o1.equals(o2) Û
o1.hashCode()==o2.hashCode()
Gleichheit und Hashcode
Objekt-Gleichheit: Beziehung von equals( ) zu hashCode()
Algorithmen, die gleiche Objekte o1 und o2 einer beliebigen Klasse mittels Hashcode suchen, basieren immer auf folgender Annahme:
o1.equals(o2)
Þ o1.hashCode()==o2.hashCode()
Da man als Klassen-Designer schlecht die Verwendung der Instanzen in einer Set oder als Schlüssel in einer HashMap verbieten kann, hat man keine Wahl (siehe hierzu 14.3.2).
![Icon](common/R.gif)
Die Methode hashCode() muss gemäß der nachfolgenden Regel implementiert werden:
hashCode()-
Implementation
1. |
Gleiche Objekte müssen den gleichen Hashcode erzeugen.3 |
2. |
Der Hashcode darf sich nicht ändern, während das Objekt in einer Kollektion ist, die auf einem Hashcode basiert. |
|
Folgerung: Die Felder, auf denen equals() und hashCode() basieren, müssen während der Zeit in der Kollektion immutable sein.3
Die Ablage und Suche von Objekten per Hashcode wird in Abb. 10.1 an einem Beispiel veranschaulicht.
Hash-Tabelle:
Einfügen und Suchen von Objekten
Abbildung 10.1 Einfügen und Suche von Objekten in eine Hashtable
Hash-Tabelle
hashCode():
erste Einschätzung!
Zum Thema »Hashcode« hier ein »first guess«, eine erste Einschätzung. Gibt es nur ein Objekt in einem Array-Slot, reicht ein anschließender Vergleich, sonst muss die Liste traversiert werden.
Performance
Hash-Tabelle:
Performance-Faktoren
Die Performance beim Eintragen und Suchen in einer Hash-Tabelle wird u.a. durch folgende Faktoren beeinflusst:
|
Array-Größe: Sie ist idealerweise eine Primzahl und steht in Relation zu der Gesamtzahl der Einträge. |
|
Hashcode: Erzeugt idealerweise für ungleiche Objekte verschiedene Zahlen. |
10.1.5 Berechnung des Hashcodes ![down](common/down.gif) ![top](common/top.gif)
![Icon](common/R.gif)
Der Algorithmus zur Berechnung des Hashcodes eines Objekts in der Methode hashCode() kann anhand folgenden Regeln erstellt werden:
Objekt:
Berechnung des Hashcodes
Es sind die Felder zu wählen, die ein Objekt identifizieren, und die auch zur Bestimmung von equals() herangezogen werden.
1. |
Der Hashcode kann aus den Hashcodes der einzelnen Felder durch Addition oder – besser noch – durch xor-Operationen ^ berechnet werden. |
|
|
Für primitive Typen ist entweder deren Wert direkt geeignet oder man verwendet die Hashcode-Berechnung der Wrapper-Klassen. |
|
|
Für Objekt-Felder verwendet man deren Hashcode. |
Beispiel
Instanzen der nachfolgenden Klasse C sind nur gleich, wenn die Werte aller drei Felder gleich sind. Die Methode hashcode() könnte dann wie folgt implementiert werden:
class C {
String s;
char c;
double d;
public C(String s, char c, double d) {
this.s=s; this.c= c; this.d= d;
}
//...
public int hashCode() {
long l = Double.doubleToLongBits(d);
return s.hashCode() ^ c
^ (int)(l ^ (l >> 32));
}
}
Erklärung: Der Hashcode von double wurde aus der Wrapper-Klasse Double übernommen. Er beruht auf einer xor-Operation der oberen mit den unteren vier Bytes.
Das folgende Code-Fragment zeigt, dass auch nahezu gleiche Objekte unterschiedliche Hashcodes haben:
C c1= new C("E-Commerce",'E',123.45),
c2= new C("E-Commerce",'E',123.4500001);
System.out.println(c1.hashCode()); // :: 1264017351
System.out.println(c2.hashCode()); // :: 1252245405
10.2 Class ![down](common/down.gif) ![top](common/top.gif)
Class:
Reflexion der Klassen-Eigenschaften
Wird eine Klasse oder ein Interface geladen, erzeugt die JVM automatisch ein Objekt der Klasse Class, das die Klassen- bzw. Interface-Eigenschaften reflektiert.
Somit gibt es keinen öffentlichen Konstruktor in Class.
Ein Class-Objekt enthält alle wichtigen Informationen über eine Klasse bzw. ein Interface. Hierzu zählen Felder, Methoden, Konstruktoren, Parameter und Modifikatoren.
Diese Klassen-Informationen sind für viele Anwendungen wie Serialisierung, Beans, Entwicklungsumgebungen und zur Benutzung unbekannter Klassen zur Laufzeit interessant.
Zugriff auf Class-Objekt:
TYPE getClass() .class
Das zu einer Klasse gehörige Class-Objekt erhält man für
|
primitive Typen mit Hilfe der Konstanten TYPE der Wrapper-Klasse |
|
Instanzen mit Hilfe der Methode getClass() |
|
Klassen durch Anfügen von .class an den Klassennamen |
Eine Klasse bzw. Class-Objekt wird normalerweise von der JVM geladen bzw. erzeugt, wenn auf sie das erste Mal zugegriffen wird. Alternativ dazu bietet sich noch ein dynamisches Laden zur Laufzeit an:
Class.forName():
dynamisches Laden einer Klasse
Eine Klasse kann mit Hilfe der statischen Methode Class.forName() und ihrem vollen Namen (fully qualified name, d.h. inklusive des Package-Namens) geladen werden.
Das Subpackage java.lang.reflect arbeitet eng mit Class zusammen. »Reflexion« wird ausführlich in Kapitel 13 behandelt, deshalb wird hier auf nähere Beispiele verzichtet.
Beispiel
Untersuchen einer Hierarchie mit Hilfe der Class-Objekte
In einem Package kap10 soll mit Hilfe von Class eine kleine Hierarchie untersucht werden.
package kap10;
interface I1 {}
interface I2 {}
class B {}
final class A extends B implements I1,I2 {}
public class Test {
public static void main(String[] args) {
Class cA= A.class, cB= B.class;
// Das Objekt zu der Klasse Class, Ausgabe
mit toString()
System.out.println(Class.class); // :: class java.lang.Class
// Package- und Klassenname zu Klasse
A
System.out.println(cA.getName()); // :: kap10.A
// Package- und Klassenname zu einem
Objekt von B
System.out.println(new B().getClass().getName());// :: kap10.B
// Superklasse zu Klasse A
System.out.println(cA.getSuperclass().getName());// :: kap10.B
// ein Interface zur Klasse A, Ausgabe
mit toString()
System.out.println(
cA.getInterfaces()[0]); // :: interface kap10.I1
// Eine Instanz von A ist eine Instanz
von B
System.out.println(cB.isInstance(new A())); // ::
true
// Typ B kann nicht nach Typ A implizit
konvertiert werden
System.out.println(cA.isAssignableFrom(cB)); // :: false
// new int[]{1} ist ein Array
System.out.println(
new int[]{1}.getClass().isArray()); // :: true
// Double.TYPE gehört zu double,
nicht Double
Double[] darr= {new Double(1.)};
System.out.println( darr.getClass().getComponentType()
==Double.TYPE); // :: false
}
}
10.3 Wrapper-Klassen ![down](common/down.gif) ![top](common/top.gif)
Immutable Wrapper-Klassen für primitive Typen
Jeder primitive Typ und void hat eine zugehörige immutable Klasse, die
|
ihren primitiven Pendant in einem Objekt kapselt |
|
den ausgeschriebenen Namen des Typs trägt |
|
nützliche Konstanten und Methoden zum Typ bereitstellt. |
Die Klasse Void stellt nur die Konstante Void.TYPE zur Verfügung.
Alle numerischen Wrapper-Klassen sind von der abstrakten Superklasse Number abgeleitet, welche nur Konvertierungs-Methoden erklärt.
Wrapper-Klassen:
für Berechnungen ungeeignet
Wrapper-Klassen sind für Berechnungen ungeeignet, da sie keine Operationen bereitstellen. Allerdings sind sie nützlich, um Strings umzuwandeln und um Werte primitiver Typen in Kollektionen einzufügen, da diese nur Objekte akzeptieren.
Beispiel
Es werden verschiedene Wrapper-Objekte zu den primitiven Typen in eine HashSet eingefügt, die mit Hilfe ihrer toString()-Methode anschließend ausgegeben wird:
public class Test {
public static void main(String[] args) {
System.out.println(
Double.parseDouble("123.45E-2")); // :: 1.2345
Keine Void-Objekte
Set hs= new HashSet();
hs.add(new Float(0./0));
hs.add(new Double("123"));
hs.add(new Integer("123"));
hs.add(new Short("123"));
hs.add(new Boolean("FALSE"));
//hs.add(new Void()); C-Fehler ¨
System.out.println(hs);
// :: [123.0, NaN, false, 123, 123]
System.out.println(s.contains(new Long("123")));
// :: false
}
}
Zu ¨: void ist kein Typ. Damit gibt es auch kein Objekt vom Typ Void.
10.4 String und StringBuffer ![down](common/down.gif) ![top](common/top.gif)
In Java gibt es zwei konkurrierende Klassen, die beide String-Handling anbieten, die
|
immutable Klasse String, die nach der Anlage eines Strings keine Änderung erlaubt |
Mutable StringBuffer: Änderungs-Operationen wie bei
C++-Strings
|
Klasse StringBuffer, die Methoden wie append(), delete(), insert() und replace() zur Bearbeitung anbietet. |
Damit ist StringBuffer verwandt mit der String-Klasse in C++.
Performance vs. Eleganz
StringBuffer
aus Performance-Gründen
Je nach String-Operation ist die Frage interessant, wann man StringBuffer der »eleganten« String-Klasse vorziehen soll:
|
Bei (mehrfachen) Änderungen eines Strings ist die Performance eines StringBuffer-Objekts deutlich höher als die eines String-Objekts.4 |
10.4.1 Compiler-Optimierung ![down](common/down.gif) ![top](common/top.gif)
Der Compiler wandelt allerdings das sehr aufwändige Anfügen von Strings mit dem Operator + automatisch in das Äquivalent append() von StringBuffer um.
Somit sind die folgenden Anweisungen äquivalent:
Compiler-
Optimierung zu einfachen String-Operationen mit +
System.out.println(
"Dies "+"ist "+"eine "+"Append-"+"Operation.");
System.out.println(new StringBuffer().append("Dies ")
.append("ist ")
.append("eine ")
.append("Append-")
.append("Operation.")
.toString());
10.4.2 Notwendigkeit der manuellen Optimierung ![down](common/down.gif) ![top](common/top.gif)
Aussagen über Compiler-Optimierungen sind »Momentaufnahmen«. Deshalb können auch die folgenden String-Operationen in neueren Versionen eines Compilers automatisch in StringBuffer-Äquivalente umgewandelt werden.
Beispiel
String[] sarr={"Dies ","ist ","eine ","Append-","Operation."};
long t;
Keine
Optimierung bei komplexen String-Operationen!
String-Version:
t= System.currentTimeMillis();
String s="";
for (int j= 0; j<1000; j++)
for (int i= 0; i<sarr.length; i++) s+=sarr[i];
System.out.println(System.currentTimeMillis()-t); // :: 3844
StringBuffer-Äquivalent:
t= System.currentTimeMillis();
StringBuffer sb= new StringBuffer();
for (int j= 0; j<1000; j++)
for (int i= 0; i<sarr.length; i++) sb.append(sarr[i]);
System.out.println(System.currentTimeMillis()-t); // :: 15
Hier ist die StringBuffer-Version über 250-mal schneller.5
Fazit
Im Zweifelsfall StringBuffer
|
Im Zweifelsfall sollte man bei Änderungen aufgrund deutlicher Performance-Vorteile StringBuffer-Operationen einsetzen. |
10.4.3 Größe eines StringBuffer-Objekts ![down](common/down.gif) ![top](common/top.gif)
![Icon](common/T.gif)
Sehr wichtig ist folgendes »Feature«6 :
Speicherbelegung eines StringBuffer-Objekts
|
Ein StringBuffer-Objekt wächst nur und kann auch mittels der Methode setLength() nicht verkleinert werden. |
System.out.println(sb.capacity());
// :: 36862
sb.setLength(0);
sb.append("a");
System.out.println(sb); // :: a
System.out.println(sb.capacity());
// :: 36862
Ergebnis: Die Wiederverwendung von sb mit einer Kapazität von mehr als 36000 Zeichen für den String "a" war keine so gute Idee.
10.5 Math und StrictMath ![down](common/down.gif) ![top](common/top.gif)
Die Math-Klasse birgt keine Überraschungen. Sie ist final, Instanzen sind nicht erlaubt und enthält als statische Methoden einen minimalen Satz an mathematischen Berechnungen für numerische Typen, überwiegend mit double-Operanden bzw. -Ergebnis.
Die meisten Methoden sind native, um der JVM zu überlassen, die Fähigkeiten der Hardware voll auszunutzen.
Math-Operationen: prozessorabhängig
|
Das Ergebnis einer Fließkomma-Berechnung ist somit nicht exakt reproduzierbar, d.h. auf unterschiedlichen Prozessoren (wie z.B. Intel/PowerPC/SPARC) kommt es zu leicht unterschiedlichen Ergebnissen. |
StrictMath:
prozessorunabhängig
Dies führte zur Einführung einer weiteren Klasse StrictMath in Java 1.3, die Math substituiert, um sicherzustellen, dass Fließkomma-Berechnungen auf allen Maschinen zum gleichen Ergebnis führen.7
Natürlich ist die Verwendung von StrictMath mit Geschwindigkeitseinbußen verbunden.
Minimalsammlung mathematischer Operationen
Neben den beiden double-Konstanten PI und E enthält Math/StrictMath die Operationen
|
abs() |
|
min() und max() |
|
round(), ceil(), floor() und IEEEremainder() |
|
random() |
|
sqrt(), pow(), exp() und log() |
sowie diverse Operationen zur Trigonometrie.
Die Namen der Methoden spiegeln offensichtlich ihre mathematische Operation wider und sind nicht erklärungsbedürftig.
Die Methode IEEEremainder() ist eine Alternative zur normalen Modulo-Operation (siehe auch 2.2.2).
Die Methode random() erzeugt double-Zahlen im (halboffenen) Intervall [0.0,1.0) und ist zur Demonstration des Singleton-Patterns interessant.
10.5.1 Singleton-Pattern und Lazy Initialization ![down](common/down.gif) ![top](common/top.gif)
Singleton-Pattern
Das Singleton-Muster, angewendet auf eine Klasse, stellt sicher, dass von ihr nur genau eine Instanz erzeugt wird (siehe Abb. 10.2).
Abbildung 10.2 Singleton-Pattern
Singleton-Klasse:
Eager-Variante
Eager-Variante: Die Implementation der Singleton-Klasse ist einfach.
class Singleton {
private static Singleton singleton= new Singleton();
private Singleton() {} // keine externe
Instanz möglich!
public static Singleton getSingleton() {
return singleton;
}
// weitere öffentliche Methoden
}
Lazy Initialization: Initialisierung nur bei Bedarf
|
Unter lazy Initialization versteht man Anlage und Initialisierung eines Objekts erst bei Bedarf. Dies spart Zeit und Speicherplatz. |
Lazy-Variante: Lazy Initialization – angewendet auf eine Singleton-Klasse – bedeutet, dass die einzige Instanz der Klasse nicht sofort beim Laden der Klasse erzeugt wird, sondern erst bei Bedarf:
Singleton-Klasse:
Lazy-Variante
class Singleton {
private static
Singleton singleton; // noch null!
private Singleton() {}
public static synchronized
Singleton getSingleton() {
if (singleton==null) singleton= new Singleton();
return singleton;
}
}
Die Erzeugung muss thread-sicher, d.h. synchronisiert erfolgen.
Ergebnis: Beim Vergleich der beiden Versionen stellt man fest, dass die Lazy-Variante für eine Singleton-Klasse nur Nachteile bringt, denn
1. |
die synchronisierte Methode von getSingleton() ist langsamer |
2. |
die Singleton-Klasse wird nur bei Bedarf von der JVM geladen, der aber erst durch den Aufruf der Methode getSingleton() entsteht. |
Aufgrund des zweiten Punkts kann dann direkt beim Laden der Klasse die Instanz angelegt werden.
10.5.2 Singleton-Komposition ![down](common/down.gif) ![top](common/top.gif)
Singleton aufgrund von Komposition:
Beispiel Random
Das Singleton-Pattern tritt in der »reinen« Form weniger häufig auf als in Form von Komposition (siehe Abb. 10.2). Die Klasse Math enthält z.B. genau ein Singleton der Klasse Random (Pseudo-Zufallszahlen-Generator)
private static Random randomNumberGenerator;
Beim Laden von Math ist es nicht notwendig, bereits eine Random-Instanz anzulegen. Diese wird erst beim Aufruf der Methode random() benötigt. Deshalb folgt der Original-Code von Sun der o.a. zweiten Version:
randomNumberGenerator von Sun
public static synchronized double random() {
if (randomNumberGenerator == null)
randomNumberGenerator = new Random();
return randomNumberGenerator.nextDouble(); // Delegation
}
In der letzten Anweisung delegiert random() die Erzeugung der Zufallszahl an die Random-Instanz.
10.5.3 Singleton und Double-Check Locking ![down](common/down.gif) ![top](common/top.gif)
![Icon](common/i.gif)
Double-Check Locking: lazy Synchronization
Die zweite (Sun-)Version hat immer noch den Nachteil der Synchronisation. Denn diese wird nur für den ersten Aufruf von getSingleton() bzw. random() benötigt, um zu verhindern, dass mehrere Threads gleichzeitig zwei Instanzen anlegen.
Eine bessere Alternative besteht darin, zuerst unsynchronisiert zu prüfen. Denn nur einmal, beim ersten Aufruf, muss die Instanz synchronisiert erzeugt werden, wobei allerdings dann noch einmal geprüft werden muss, ob die Instanz immer noch null ist.8
Singleton-Klasse:
Lazy-und Double-Check
Double-Check-Variante: Singleton mit Double-Check.
class Singleton {
private static Singleton singleton;
private Singleton() {}
Double-Check: performant und
thread-sicher!
public static Singleton getSingleton() {
if (singleton==null) // 1. Check
synchronized(Singleton.class) {
if (singleton==null) // 2. Check
singleton= new Singleton();
}
return singleton;
}
//...
}
Die Methode random() hätte dann folgende Implementierung:9
//...
public static double random() {
if (randomNumberGenerator == null)
synchronized(Math.class) {
if (randomNumberGenerator == null)
randomNumberGenerator = new Random();
}
return randomNumberGenerator.nextDouble();
}
//...
10.5.4 Probleme mit komplexen Zahlen ![down](common/down.gif) ![top](common/top.gif)
Gravierende
Nachteile einer Klasse Complex
Was man schmerzlich vermisst, sind komplexe Zahlen und deren Operationen. Leider gibt es in Java keinen primitiven Typ complex. Also müssen komplexe Zahlen als Klasse Complex nachgebildet werden.
|
Es gibt keine implizite Typumwandlung von double nach Complex. |
|
Es gibt keine Werte-Semantik bei Zuweisung und Gleichheit. |
|
Es wird ein zeitaufwendiger Konstruktor zur Erschaffung benötigt. |
|
Es sind keine normalen Operatoren erlaubt. |
10.6 System ![down](common/down.gif) ![top](common/top.gif)
System:
Anbindung an das Betriebssystem
Die Klasse System kapselt wichtige System-Ressourcen und ist wie Math eine Ansammlung von statischen Methoden, allerdings ohne die Möglichkeit, Instanzen oder Subklassen anzulegen.
System: Überblick
Die Methoden von System decken einen weiten Bereich ab.
|
Die Standard-Ein- bzw. Ausgabe wird mit Hilfe der static final-Stream-Variablen in, out bzw. err (für Fehler) vorgenommen. |
|
Auf System-Eigenschaften kann man mit Hilfe von Properties, einer Subklasse von Hashtable, zugreifen. |
Properties enthält String-Paare, d.h. über Schlüssel abrufbare Konfigurations-Informationen, wobei wichtige System-Informationen in der Plattform festgelegt sind.
|
Es gibt Methoden zum Laufzeitsystem, die an die Klasse Runtime delegiert werden, wie z.B. der Programmabbruch durch exit(). |
|
Man hat Zugriff auf den Sicherheitsmanager SecurityManager. Der Sicherheitsmanager prüft, ob Operationen erlaubt sind. |
|
Es existieren diverse Methoden wie z.B. das Kopieren von Arrays mit arraycopy(), Zeitabfrage mit currentTimeMillis() oder identity HashCode(), um ggf. noch auf den Original-Hashcode von Object zugreifen zu können. |
Nachfolgend werden anhand von kleinen Beispielen zu jedem Bereich – mit Ausnahme des Sicherheitsmanagers – wichtige Methoden vorgestellt.
Da Streams im nächsten Kapitel besprochen werden, soll in den beiden folgenden Abschnitten nur kurz die »normale« Verwendung der Standard-Ein- und Ausgabe und die damit verbundene Problematik der Zeichen-Codierung angesprochen werden.
10.6.1 Byte-orientierte Konsol-Ausgabe ![down](common/down.gif) ![top](common/top.gif)
Konsole vs. GUI
Professionelle Programme müssen ihre Ein- und Ausgaben zum Benutzer hin über eine grafische Schnittstelle (GUI) wie z.B. Swing abwickeln (siehe Kapitel 15).
Einer Java-Tradition folgend, wird auch in diesem Buch die Ausgabe für Beispiele und Programmfragmente minimalistisch mit der Konsole System.out erledigt.
System.out bzw. System.err
Obwohl bereits verwendet, soll hier der Begriff »Stream« präzisiert werden:
Stream:
Sequenz von Daten
|
Ein Stream bzw. Strom steht für eine serielle Ein- oder Ausgabe von beliebig vielen Daten, entweder byte- oder zeichenorientiert. |
Der Typ von out ist PrintStream, eine indirekte Subklasse der Basisklasse OutputStream für byte-orientierte Ausgabe, kurz Byte-Streams genannt.
System.out: Standard-Konsol-Ausgabe
Die Streams out bzw. err sind per Default direkt mit der System-Konsole verbunden, brauchen also nicht zuerst geöffnet zu werden.
10.6.2 Zeichen-Codierung und Unicode ![down](common/down.gif) ![top](common/top.gif)
Unicode: »universelle« Codierung von Zeichen
Java codiert intern jedes Zeichen in zwei Bytes, basierend auf Unicode, einer umfassenden Zuordnung von Zeichensymbolen weltweit verwendeter Sprachen zu ganzen Zahlen.
|
Bei einer Codierung repräsentiert die Nummer des Tabellenplatzes, an dem das grafische Zeichensymbol steht, dieses Zeichen. |
Vorteile von Unicode
Die Unicode-Codierung hat folgende entscheidende Vorteile:
1. |
Sie umfasst 65.536 Tabelleneinträge, die mit Hilfe von \u0000.. \uFFFF auch direkt eingegeben werden können. |
2. |
Die Tabelleneinträge 0..127 bzw. \u0000..\u007F, d.h. die ersten sieben Bits, stimmen mit der ISO U.S. ASCII-Codierung überein.10 |
3. |
Die Tabelleneinträge 0..255 bzw. \u0000..\u00FF oder das unterste Byte stimmen mit der Codierung ISO 8859-1 (Latin 1) überein. |
4. |
Alle anderen weltweit gebräuchlichen Alphabete sind in Gruppen, d.h. in kontinuierliche Blöcke eingeteilt, die einen Namen tragen (wie z.B. Thai für den Block \u0E00..\u0E7F). |
5. |
Sie ist international anerkannt und wird zunehmend in alle Betriebssysteme als nativer Zeichensatz integriert. |
Transformation von Unicode
Da die Realisierung des letzten Punkts noch aussteht, muss bei der Ein- oder Ausgabe von internen Unicode-Zeichen eine Transformation bei der Kommunikation zwischen der JVM und dem jeweiligen Betriebssystem stattfinden.
Endianness
Big- und little-endian Format
Selbst wenn das Betriebssystem ebenfalls Unicode verwendet, muss zumindest die Low-High-Anordnung der zwei Bytes (Endianness) im Speicher bekannt sein, bezeichnet als big- oder little-endian Format.
Unicode-Formate
Die Transformation ist verlustfrei, wenn die volle Unicode-Zeichen-Information erhalten bleibt.
Dies gilt für
Unicode-Formate:
Big (unmarked) Small UTF-8
|
UnicodeBig: zwei Byte Startmarkierung, big-endian Zeichenformat |
|
UnicodeSmall: zwei Byte Startmarkierung, little-endian Format |
|
UnicodeBigUnmarked: unmarkiertes UnicodeBig |
|
UTF-8: ein Byte für ASCII-Zeichen, ansonsten in Netzwerk-Byte-Ordnung, d.h. höchstwertiges Byte zuerst, wie in Tabelle 10.1 angegeben11 |
Tabelle 10.1 UTF8 Codierung für Nicht-ASCII-Zeichen
\u0080..\u07FF |
1.,2.Byte: 110 b10..b6 10 b5..b0 |
\u0800..\uFFFF |
1.-3.Byte: 1110 b15..b12 10 b11..b6 10 b5..b0 |
Für Übertragungen vorwiegend im ASCII-Format ist UTF-8 gut geeignet.
10.6.3 Zeichen- vs. Byte-Streams ![down](common/down.gif) ![top](common/top.gif)
Zeichen- vs. Byte-Streams
Zeichen-Streams sind speziell auf die Ein- und Ausgabe von Unicode-Zeichen ausgelegt und erlauben teilweise die Angaben der zu verwendenden Codierung.
Sie können einerseits direkt oder aber über Byte-Streams miteinander kommunizieren (Näheres siehe 11.6.5).
Die Verwendung von reinen Byte-Streams ist nur für Zeichen bis \u00FF geeignet und verwendet immer nur die Default-Codierung des jeweiligen Betriebssystems.
Damit ist sie nicht gerade für die Zeichen-Kommunikation in heterogenen Netzwerken geeignet (vgl. dazu ausführlicher 11.6).
10.6.4 Default-Codierung und System.out ![down](common/down.gif) ![top](common/top.gif)
Codierung von Zeichensätzen unter Windows
Der Default-Zeichensatz von Windows NT ist Cp1252 (Windows Latin 1) und stimmt in weiten Bereichen mit ISO-Latin-1 überein.
Leider gilt dies nicht für eine DOS-Konsole unter Windows NT, die den Zeichensatz Cp850 (DOS Latin 1) verwendet. Hier stimmt nur der ASCII-Teil überein.
Default-
Codierung
Bei der Default-Codierung werden von der JVM Unicode-Zeichen bis \u00FF so gut wie möglich in die Code-Tabelle des Betriebssystem abgebildet.
Abfrage der aktuellen Codierung
Díe aktuelle Codierung des Betriebssystems kann mit dem Schlüssel "file.encoding" abgefragt werden:
System.out.println(System.getProperties().
getProperty("file.encoding")); // :12 : Cp1252
Zeichen, die per Default-Codierung nicht abgebildet werden können, werden als ? (Code \u003F) dargestellt.
print() bzw. println() benutzen Default-
Codierung
Alle print()- bzw. println()-Methoden der Byte-Streams out bzw. err
|
unterliegen der Default-Codierung |
|
basieren auf einer write()-Methode, die ohne Default-Codierung nur das untere Byte des Unicode-Zeichens an das Betriebssystem übergibt (welches Latin-1-Code verstehen muss) |
Beispiele
System.out.write('A' | 0xFF00);
System.out.flush(); // :: A
System.out.write('A' & 0xFF);
System.out.write('\n'); // :: A
System.out.println((char)('A' | 0xFF00));
// :: ?
System.out.println((int)'Ä'+":"+'Ä'); // Win-NT-Fenster ::
196:Ä
// DOS-Fenster
:: 196:–
Zeichen-Ausgabe von PrintStream System.out
System.out ist eine Instanz von PrintStream, die von OutputStream die Byte-Ausgabe write(int i) erbt bzw. überschreibt.
Neben print() bzw. println() kann somit write() direkt verwendet werden (siehe Abb. 10.3).
System.out-Mechanismus
Abbildung 10.3 Zeichen-Ausgabe mittels System.out
Zur Wirkung der Methoden:
write() und flush()
|
Die Methode write() ignoriert das obere Byte, die Methode print() gibt dagegen alle Zeichen oberhalb von \u00FF als ? aus. |
|
Die Methode flush() ist dazu da, im Stream gepufferte Zeichen, die bis dahin noch nicht ausgegeben wurden, tatsächlich herauszuschreiben, wobei die Ausgabe des Zeilenende-Zeichens '\n' automatisch ein flush() erzeugt. |
|
Die Ausgaben von System.out werden mit einem Lock auf out synchronisiert. |
Beispiele
Im folgenden Code-Fragment wird die getBytes()-Methode der String-Klasse verwendet, zuerst ohne Argument, d.h. Default-Codierung, dann mit Angabe der Codierung:
Angabe der Codierung bei Ausgabe mit write()
byte[] sbuf= "AÄ\n".getBytes();
try {
System.out.write(sbuf); // Win-NT-Fenster :: AÄ ¨
} catch (IOException e) {}
String s= "AÄ";
try {
sbuf= s.getBytes("Cp850"); // DOS Latin
1-Encoding
System.out.write(sbuf); // Win-NT-Fenster ::
A_ ¦
// DOS-Fenster
:: AÄ
} catch (Exception e) {}
Zu ¨: ISO-Latin 1 und Windows Latin 1 stimmen zumindest in den Positionen der Umlaute überein.
Zu ¦: Um die Umlaute DOS-gerecht auszugeben, muss die Codierung auf Cp850 gewechselt werden. Dann wird auch in der DOS-Konsole eine korrekte Ausgabe erzeugt (aber natürlich nicht mehr im NT-Fenster!).
Im nächsten Code-Fragment wird die UTF8-Codierung für ASCII in einem Byte bzw. für Latin 1 in zwei Bytes demonstriert:
Umwandlung in UTF-8
try {
sbuf= s.getBytes("UTF8");
// s, sbuf wie oben
} catch (UnsupportedEncodingException e) {}
System.out.println(sbuf.length); // ::
3
for (int i=0; i<sbuf.length;i++) {
s= Integer.toHexString(sbuf[i]);
System.out.print(
s.substring(s.length()-2)+" "); // :: 41 c3 84
}
Die UTF8-Codierung ist also bei »normalen« Zeichen eine effiziente Speicherung für Unicode.
10.6.5 Byte-orientierte Eingabe mit System.in ![down](common/down.gif) ![top](common/top.gif)
Tastatureingaben vom Standard-Terminal können für sehr einfache Kommandos oder zu Debugging-Zwecken recht einfach über System.in erfolgen.
System.in:
Default- Anbindung an Standard-Tastatur
Das Feld in ist deklariert als Typ der abstrakten Basisklasse InputStream und wird bei der Initialisierung von System
|
als Instanz von FileInputStream instanziert und |
|
mit der Standard-Tastatur verbunden. |
Somit kann die Methode read() direkt verwendet werden.
Spiegelbildlich zur Ausgabe ist auch die Eingabe byte-orientiert. Die Interpretation der empfangenen Bytes als Zeichen muss sich somit ebenfalls an der Default-Codierung des Betriebssystems orientieren.
Decodierung mit Hilfe von String
Ist die Zeichen-Codierung bekannt, kann mit Hilfe eines String-Konstruktors der empfangene Byte-Stream in ein Unicode-String umgewandelt werden.
Decodierung von Byte-Arrays mit Hilfe des String-Konstruktors
Dazu wird dem Konstruktor von String das empfangene Byte-Array und die Codierung übergeben:
public String(byte[] byteArray, String encoding)
System.in.read()
Konzeption von read()
Die Methode System.in.read() ist recht eigenwillig konzipiert:
|
read() liest ein oder mehrere Zeichen von der Standard-Tastatur in einen internen Puffer. |
|
Erst bei Eingabe des Zeilenende-Zeichens '\n' bzw. \u000A gibt read() die eingelesenen Zeichen (inklusive des Zeilenendes) als einzelne int-Werte aus. |
|
Alle eingegebenen Zeichen (außer dem Zeilenende) erscheinen auf der Konsole sofort als »Echo« und können nicht unterdrückt werden. |
|
Die eingegebenen Zeichen stimmen oberhalb des ASCII-Bereichs nicht unbedingt mit den Zeichen auf der Tastatur überein. |
Die Eingabe von nur einem Zeichen muss somit durch die Zeilenende-Taste [ENTER] abgeschlossen werden:
int ci= System.in.read() // mit [ENTER] zu
beenden
Fazit
read() nur für Debugging-Zwecke
System.in für die Eingabe ziemlich ungeeignet und höchstens für Debugging bzw. kleine Tests zu gebrauchen.13
Beispiel
Im folgenden Code-Fragment werden mit Eingabe des Zeichens '\n' bzw. [ENTER] die vorherigen Zeichen in den externen Puffer buf geschrieben:
read()-Probleme mit Nicht-ASCII-Zeichen
int ci, i=0;
byte[] buf= new byte[10];
try {
while ((ci= System.in.read()) != '\n'
&& i<buf.length)
buf[i++]= (byte)ci; // Tastatureingabe: öäüÖÄÜßé
System.out.write(buf,0,i); // Win-NT :: öäüÖÄÜß´e
// DOS-Fenster :: öä?Ö?Üßé
} catch (IOException e) {}
Ergebnis: Die Zeichen einer Standard-PC-Tastatur werden oberhalb von ASCII unter Windows NT 4.0 in Fenstern mit unterschiedlicher Codierung auch unterschiedlich interpretiert.
10.6.6 System-Properties ![down](common/down.gif) ![top](common/top.gif)
Properties: System-Tabelle
in Form von Schlüssel/Werte-Paaren
System enthält eine Instanz der Klasse java.util.Properties, die relevante Betriebssystem-Informationen jeweils in Form zweier Strings – Schlüssel und zugehöriger Wert – bereitstellt.
Für Properties wurde eine bestenfalls merkwürdig zu nennende Implementierung gewählt:
Properties:
Subklasse von Hashtable
|
Properties ignoriert die Semantik einer Is-A-Beziehung und ist als Subklasse von Hashtable deklariert.14 |
Um nur String-Einträge zu setzen bzw. zu lesen, stellt Properties zusätzlich zwei eigene Methoden zur Verfügung:
Schlüssel-Werte-Paare: nur
Strings erlaubt
public synchronized Object setProperty(String key,String val)
public String getProperty(String key)
Diese sollen anstatt der allgemeinen Hashtable-Methoden put() und get() verwendet werden.
|
Da weder put() noch get() so überschrieben wurden, dass sie Ausnahmen auslösen, können diese für beliebige andere Einträge benutzt werden. |
Damit wäre Properties schlichtweg korrumpiert.
Das Lesen einer einzelnen Property wurde bereits zur Abfrage der Default-Codierung vorgestellt.
Beispiel
Ausgabe der System-Tabelle
Das folgende Code-Fragment gibt alle System-Properties aus:
Properties sp= System.getProperties();
Enumeration enum= sp.propertyNames();
while (enum.hasMoreElements()) {
String key= (String) enum.nextElement();
System.out.println(key+": "+sp.getProperty(key));
}
Hashtable und der Iterator Enumeration stellen eigentlich Legacy-Code aus den frühen Tagen von Java dar (siehe Kollektionen in Kapitel 14).
10.6.7 Methode: load() bzw. loadLibrary() ![down](common/down.gif) ![top](common/top.gif)
Laden von
systemabhängigen Bibliotheks-Funktionen
Mit Hilfe der Methode load(String filename) bzw. loadLibrary(String librayName) werden – meistens in C geschriebene – Bibliotheks-Funktionen geladen, die dann über native Java-Wrapper-Methoden angesprochen werden.
Bei load() muss der komplette Zugriffspfad angegeben werden, wogegen loadLibrary() nur den Namen der Bibliothek benötigt, um diesen in einen plattformspezifischen String umzuwandeln.
Bei Windows NT haben die System-Bibliotheken die Dateierweiterung .dll und können in Unterverzeichnissen abgelegt werden, die in der Windows-Umgebungs-Variablen PATH aufgeführt sind.
Das Laden der Bibliothek erfolgt in der Regel im statischen Initialisierer der Klasse, um sicherzustellen, dass die nativen Funktionen vor jedem Aufruf einer Methode zur Verfügung stehen:
static {
// lädt "anylib.dll" aus einem Unterverzeichnis in PATH
System.loadLibrary("anylib");
}
10.6.8 Methode: identityHashCode() ![down](common/down.gif) ![top](common/top.gif)
identityHashCode(): Hashcode von Object
Wird in einer Klasse hashCode() überschrieben, steht zu den Instanzen das originale hashCode()-Ergebnis von Object nicht mehr zu Verfügung. Abhilfe schafft dann die Methode
int identityHashCode(Object o),
die jedes Objekt eindeutig identifiziert.
10.6.9 Methode: currentTimeMillis() ![down](common/down.gif) ![top](common/top.gif)
Die Methode currentTimeMillis() misst die Zeit in Millisekunden, die seit dem 01.01.1970 vergangen ist und gibt sie als long zurück.
currentTimeMillis(): Abfrage der Systemzeit
Zeitmessung sowie Wartezeit in sleep() sind, abhängig vom Betriebssystem, mit einer gewissen Latenz behaftet:
long start= System.currentTimeMillis();
try {
Thread.sleep(100);
} catch (Exception e) {}
System.out.println(System.currentTimeMillis()-start); // :: 109
10.6.10 Methode: arraycopy() ![down](common/down.gif) ![top](common/top.gif)
Das Kopieren von beliebigen Arrays kann mit der Methode
arraycopy():
schnelles Kopieren von beliebigen Arrays
arraycopy(Object src,int spos,Object dst,int dpos,int
len);
effizient und sicher erledigt werden, da jede Art von Fehler wie z.B. inkompatible Element-Typen bestimmte RuntimeExceptions auslösen.
Beispiel
Zuerst werden die Elemente des Arrays sarr verschoben:
String[] sarr= {"A","B","C","D"};
Object[] oarr= {null,"D"};
System.arraycopy(sarr,0,sarr,1,sarr.length-1);
for (int i=0; i<sarr.length;i++)
System.out.print(sarr[i]); // :: AABC
Anschließend wird oarr nach sarr kopiert:
System.arraycopy(oarr,0,sarr,0,
Math.min(sarr.length,oarr.length));
for (int i=0; i<sarr.length;i++)
System.out.print(sarr[i]); // :: nullDBC
10.6.11 Methode: exit() ![down](common/down.gif) ![top](common/top.gif)
exit():
Beenden einer Java-App
Die Methode exit() terminiert die JVM, d.h., die Methode führt einen Programmabbruch durch, wobei ein Status verschieden von Null einen anormalen Abbruch nach außen signalisieren soll.
10.7 Process ![down](common/down.gif) ![top](common/top.gif)
Die Klasse Process ist abstrakt, d.h. wird indirekt durch den Aufruf von Runtime.exec() erschaffen.
Process:
Abstraktion eines Prozesses
Ein Prozess kann jede eigenständige Applikation sein und ist nicht auf Java-Apps beschränkt. Ein Prozess läuft auf keinen Fall in derselben JVM.
Der Einsatz bzw. der Start von Prozessen ist somit sehr eingeschränkt, da plattformabhängig.15
|
In der Regel sind damit Prozesse wieder auf Java-Apps, interaktive Angaben bzw. Konfigurationsdateien beschränkt. |
Methoden in Process
Process hat nur wenige Methoden:
|
waitFor() suspendiert den Aufrufer so lange, bis der Prozess endet.16 |
|
destroy() zerstört den Prozess. |
|
exitValue() liefert den Rückgabe-Code des beendeten Prozesses.17 |
|
getInputStream() bzw. getErrorStream() sind zwei Streams, von denen Bytes gelesen werden können, die der Prozess zur Standard-Ausgabe bzw. -Fehlerausgabe sendet. |
|
getOutputStream() sendet Bytes zur Standard-Eingabe des Prozesses. |
Ein Beispiel hierzu ist im nächsten Abschnitt aufgeführt.
10.8 Runtime ![down](common/down.gif) ![top](common/top.gif)
Es gibt einige Methoden der Klasse Runtime, die ebenfalls in System verfügbar sind.
Dazu zählen auch Methoden zur Speicherbereinigung, die aber entweder »deprecated« oder in ihrer Wirkung umstritten sind (siehe 5.9).
Erwähnenswert ist dagegen die Methode exec(), die zusammen mit der Klasse Process und System.exit() verwendet wird.
10.8.1 Methode: exec() ![down](common/down.gif) ![top](common/top.gif)
Runtime.exec() startet Prozesse
Die Methode exec() gibt es in sechs Varianten, um einen Prozess mit optionalen Startparametern, Environment-Variablen18 oder einem Arbeitsverzeichnis zu starten. Werden keine Environment-Variablen gesetzt, erbt der neue Prozess diese vom startenden:
exec()-Varianten für diverse
Startarten
public Process exec(String cmdarray[], [
String envp[] ],
[ File dir ]
) throws IOException;
public Process exec(String command, [
String envp[] ],
[ File dir ]
) throws IOException;
In beiden exec()-Versionen sind jeweils die letzten beiden Parameter optional.
Beispiele
Die erste Test-Version beschränkt sich auf eine reine Java-Applikation, ist also plattformabhängig, die folgende Version nicht:
Zuerst ruft Test als Prozess ExecTest auf. Dazu wird mit Hilfe der Runtime-Methode exec() ein neue JVM gestartet, die ExecTest ausführt.
package kap10;
Start einer Java-Applikation
public class ExecTest {
public static void main(String[] args) {
// entspricht Runtime.getRuntime().exit(status);
System.exit(1); // simuliert anormalen Abbruch!
}
}
// --- 1. Version: ruft ExecTest mit neuer
JVM auf ---
// Test startet ExecTest als neuen Prozess
public class Test {
public static void main(String[] args) {
try {
Process p= Runtime.getRuntime().exec(
"C:/JBuilder4/jdk1.3/bin/java.exe -cp "
+"C:/java-buch/classes kap10.ExecTest");
System.out.println(p.waitFor()); // :: 1
}
catch (Exception e) { System.out.println(e); }
}
}
Start eines Windows-Programms
// -- 2. Version: ruft Windows Wordpad mit Textdatei
auf --
public class Test {
public static void main(String[] args) {
try {
Process p= Runtime.getRuntime().exec( new String[] {
"C:/Programme/Windows NT/Zubehör/WORDPAD.EXE",
"C:/Java-Buch/TempValues.txt"} );
System.out.println(p.waitFor());
// :: 0
}
catch (Exception e) { System.out.println(e); }
}
}
Das letzte Beispiel ist eine Erweiterung des ersten.
Test übergibt zwei Argumente an ExecTest, die diese mit System.out ausgibt. Test verbindet sich allerdings mit der Standard-Ausgabe, sodass es die Ausgabe von System.out wieder empfängt und anschließend ausgibt:
Start einer Java-Applikation mit Verbindung zur Standard-Ausgabe
package kap10;
public class ExecTest {
public static void main(String[] args) {
for (int i= 0; i<args.length; i++)
System.out.println(args[i]);
System.exit(0);
}
}
public class Test {
public static void main(String[] args) {
try {
Process p= Runtime.getRuntime().exec(
"C:/JBuilder4/jdk1.3/bin/java.exe -cp "
+"C:/java-buch/classes kap10.ExecTest Hallo Welt");
// getInputStream(): Verbindung mit
Standard-Ausgabe
InputStream in = p.getInputStream();
byte[] barr= new byte[100];
System.out.print(new String(barr,0,in.read(barr)));
System.out.println(p.waitFor());
} catch (Exception e) { System.out.println(e); }
}
}
waitFor()
suspendiert den aufrufenden Thread
Erklärung: Die Methode waitFor() der Klasse Process blockiert dann den laufenden Hauptthread, bis der externe Prozess terminiert und liefert als Ergebnis den Exit-Code des ausgeführten Prozesses:
Alternativ könnte die Methode exitValue() verwendet werden, wobei diese allerdings bei einem noch laufenden Prozess eine Ausnahme auslöst.
10.9 Zusammenfassung ![down](common/down.gif) ![top](common/top.gif)
Das Package java.lang enthält mit Object und Class fundamentale Bausteine der Java-Sprache, ohne die die Sprache schlicht nicht funktionsfähig wäre. Die in den vorherigen Kapiteln noch nicht behandelten Methoden der beiden Klassen werden besprochen bzw. bei Class an einem Beispiel vorgestellt.
Von zentraler Bedeutung für die Vollständigkeit einer Klasse sind equals() und hashCode(), deren Implementation und Beziehung detailliert erörtert wird.
Die Methoden in Class bilden die Basis für Reflexion und dynamisches Laden von Klassen zur Laufzeit. Es werden nur kurz die Methoden vorgestellt, da der Einsatz von Class im Zusammenhang mit Reflexion erneut behandelt wird.
Ein Vergleich der Klassen String und StringBuffer zeigt ihre Einsatzschwerpunkte und gibt Hinweise auf Performance-Engpässe.
Zusammen mit der Klasse Math wird das Singleton-Pattern in reiner Form und in Varianten an Code-Mustern vorgestellt. Es werden die Techniken eager vs. lazy Initialization sowie Double-Check Locking an einem Beispiel erklärt.
Einen weiteren Schwerpunkt bildet die Klasse System. Die Standard-Ein- bzw. Ausgabe verbunden mit Unicode und der Problematik der Codierung in Hinblick auf Byte-Streams wird an vielen kleinen Beispielen demonstriert. Properties und diverse nützliche Methoden beenden den System-Abschnitt.
Abschließend werden die Klassen Process und Runtime zum Starten externer Prozesse bzw. Applikationen anhand von drei Beispielen vorgestellt.
Streams werden insgesamt nur am Rand behandelt, da diese das Thema des folgenden Kapitels sind.
10.10 Testfragen ![top](common/15x15leer.gif) ![top](common/top.gif)
Zu jeder Frage können jeweils ein oder mehrere Antworten bzw. Aussagen richtig sein.
1. Welche Aussagen sind richtig?
2. Welche Anweisungen sind richtig?
3. Welche Zeilen sind Teil der Ausgabe der folgenden Applikation?
public class Test {
public static void main(String[] args) {
StringBuffer sb= new StringBuffer("test");
if (sb.equals("test")) System.out.println("true-1");
if ("test".equals(sb)) System.out.println("true-2"); ¬
if (sb.equals(new StringBuffer("test")))
System.out.println("true-3");
if (sb.equals(sb)) System.out.println("true-4");
System.out.println("ende");
}
}
A: true-1
B: true-2
C: true-3
D: true-4
E: Keine, da die Klasse einen Fehler beim Kompilieren erzeugt.
F: Keine, da bei der Ausführung die Zeile ¬ eine Ausnahme erzeugt.
4. Welche Aussagen sind zu folgender Applikation richtig?
public class Test {
public static void main(String[] args) {
StringBuffer sb1= new StringBuffer("a"), sb2= sb1.append("bc");
String s1= "a", s2= s1+"bc";
System.out.println(s1==s2); ¨
System.out.println(sb1==sb2); ¦
System.out.println(s1.equals(s2)); Æ
System.out.println(sb1.equals(sb2)); Ø
}
}
A: Zeile ¨ erzeugt die Ausgabe: true
B: Zeile ¦ erzeugt die Ausgabe: true
C: Zeile Æ erzeugt die Ausgabe: true
D: Zeile Ø erzeugt die Ausgabe: true
5. Welche Aussagen sind zu folgender Applikation richtig?
public class Test {
public static void main(String[] args) {
double d= Math.random();
System.out.println(d); ¨
System.out.println(Math.floor(d)); ¦
System.out.println(Math.ceil(d)); Æ
System.out.println(Math.round(d)); Ø
}
}
A: Zeile ¨ erzeugt niemals die Ausgabe: 1.0
B: Zeile ¨ erzeugt niemals die Ausgabe: 0.0
C: Zeile ¦ erzeugt immer die Ausgabe: 0.0
D: Zeile Æ erzeugt immer die Ausgabe: 1.0
E: Zeile Ø erzeugt immer die Ausgabe: 0
F: Zeile Ø erzeugt immer die Ausgabe: 1
6. Welche Aussagen sind zu System richtig?
System.Properties p= new Properties();
B: System.exit(0) beendet alle Threads einer Applikation.
C: System.exit(0) beendet die JVM.
Object arr= new Object[4]; System.arraycopy(arr,0,arr,1,0);
1 In einer Mitarbeiter-Hierarchie (Basisklasse Employee) werden zwei Mitarbeiter als gleich angesehen, wenn sie dieselbe Personal-Nummer haben. Ein Mitarbeiter kann natürlich aufgrund seiner Karriere Objekte der Subklassen Azubi bis Manager »hinterlassen«. Die Personal-Nummer ist dann nur in Employee als Feld enthalten.
2 Wie verhält es sich im Beispiel »Mitarbeiter« der letzten Fußnote?
3 Die Umkehrung ist nicht notwendig.
4 Erzeugung und Entsorgung von immutable String-Objekten kosten Zeit (siehe Beispiel).
5 Getestet unter JBuilder 3.5. JBuilder 4.0 gibt sogar bei der StringBuffer-Version
häufig nur einen Zeitwert von 0 an.
6 MS-Jargon bei Problemen.
7 Die Algorithmen basieren auf fdlibm (Freely Distributable Math Library), die Sun in C realisiert hat.
8 Ein anderer Thread könnte ja aufgrund einer parallelen Prüfung bereits eine Instanz angelegt haben.
9 Soweit zu erkennen, muss nextDouble() nicht synchronisiert sein, da die interne Methode next() synchronisiert ist.
10 Die ASCII-Codierung ist der »kleinste gemeinsame Nenner« nahezu aller Betriebssysteme wie z.B. DOS, Unix, Windows oder Mac-OS.
11 Bedeutung: b0..b15 stehen für die maximal 16 zu übertragenden Bits.
12 Windows NT 4.0
13 Dies ist auch ein Grund, warum System.in im Gegensatz zu System.out selbst für einfache Beispiele gemieden wird.
14 Da nicht alle Methoden der Klasse Hashtable in Properties sinnvoll sind, sondern sogar zerstörerisch wirken können, ist dies ein Fall für das Delegations-Pattern: Properties müsste intern ein private deklariertes Hashtable-Objekt enthalten (siehe hierzu auch 6.7).
15 MS ist kein Freund von Linux!
16 Analog zu join() bei Threads.
17 Vorsicht, löst bei noch laufendem Prozess eine Ausnahme aus.
18 Environment-Variablen werden als String-Array in der Form name=value erwartet.
|