Galileo Computing <openbook>
Galileo Computing - Programming the Net
Galileo Computing - Programming the Net


Java 2 von Friedrich Esser
Designmuster und Zertifizierungswissen
Zum Katalog
gp Kapitel 10 Package java.lang
  gp 10.1 Object
  gp 10.2 Class
  gp 10.3 Wrapper-Klassen
  gp 10.4 String und StringBuffer
  gp 10.5 Math und StrictMath
  gp 10.6 System
  gp 10.7 Process
  gp 10.8 Runtime
  gp 10.9 Zusammenfassung
  gp 10.10 Testfragen

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.


Galileo Computing

10.1 Object  downtop

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.


Galileo Computing

10.1.1 Overriding equals()  downtop

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.

gp  Overriding von equals() muss die o.a. Signatur beachten, sonst ist es Overloading (siehe 5.2).

Icon
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
} }

Galileo Computing

10.1.2 getClass() vs. instanceof  downtop

Icon

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 Bb 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

Der Cast beim Vergleich in der letzten Anweisung von ECourse.equals() führt dann zur Ausnahme.

Symmetrische Methode: keine asymmetrischen Operationen
verwenden

Fazit

gp  Symmetrische Methoden sollten keine asymmetrischen Operationen auf ihren Argumenten anwenden.

Galileo Computing

10.1.3 Semantik von Gleichheit  downtop

Bedeutung von »gleich« ist domänen-
spezifisch

Im Gegensatz zu »Identität« ist der Begriff »Gleichheit« domänenspezifisch, d.h. nicht generell zu definieren:

gp  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.

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.

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:

gp  Die Signatur von equals() darf zwar nicht geändert werden, aber das Template in 10.1.1 muss für Interfaces angepasst werden.

Galileo Computing

10.1.4 Beziehung zwischen equals() und hashCode()  downtop

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

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.

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
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:

gp  Array-Größe: Sie ist idealerweise eine Primzahl und steht in Relation zu der Gesamtzahl der Einträge.
gp  Hashcode: Erzeugt idealerweise für ungleiche Objekte verschiedene Zahlen.

Galileo Computing

10.1.5 Berechnung des Hashcodes  downtop

Icon

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.
    gp  Für primitive Typen ist entweder deren Wert direkt geeignet oder man verwendet die Hashcode-Berechnung der Wrapper-Klassen.
    gp  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

Galileo Computing

10.2 Class  downtop

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

gp  primitive Typen mit Hilfe der Konstanten TYPE der Wrapper-Klasse
gp  Instanzen mit Hilfe der Methode getClass()
gp  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 }
}

Galileo Computing

10.3 Wrapper-Klassen  downtop

Immutable Wrapper-Klassen für primitive Typen

Jeder primitive Typ und void hat eine zugehörige immutable Klasse, die

gp  ihren primitiven Pendant in einem Objekt kapselt
gp  den ausgeschriebenen Namen des Typs trägt
gp  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.


Galileo Computing

10.4 String und StringBuffer  downtop

In Java gibt es zwei konkurrierende Klassen, die beide String-Handling anbieten, die

gp  immutable Klasse String, die nach der Anlage eines Strings keine Änderung erlaubt

Mutable StringBuffer: Änderungs-Operationen wie bei
C++-Strings 

gp  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:

gp  Bei (mehrfachen) Änderungen eines Strings ist die Performance eines StringBuffer-Objekts deutlich höher als die eines String-Objekts.

Galileo Computing

10.4.1 Compiler-Optimierung  downtop

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());

Galileo Computing

10.4.2 Notwendigkeit der manuellen Optimierung  downtop

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.

Fazit

Im Zweifelsfall StringBuffer

gp  Im Zweifelsfall sollte man bei Änderungen aufgrund deutlicher Performance-Vorteile StringBuffer-Operationen einsetzen.

Galileo Computing

10.4.3 Größe eines StringBuffer-Objekts  downtop

Icon

Sehr wichtig ist folgendes »Feature« :

Speicherbelegung eines StringBuffer-Objekts

gp  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.


Galileo Computing

10.5 Math und StrictMath  downtop

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

gp  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.

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

gp  abs()
gp  min() und max()
gp  round(), ceil(), floor() und IEEEremainder()
gp  random()
gp  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.


Galileo Computing

10.5.1 Singleton-Pattern und Lazy Initialization  downtop

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

gp  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.


Galileo Computing

10.5.2 Singleton-Komposition  downtop

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.


Galileo Computing

10.5.3 Singleton und Double-Check Locking  downtop

Icon
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.

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:

  //...
  public static double random() {
    if (randomNumberGenerator == null)
      synchronized(Math.class) {
        if (randomNumberGenerator == null)
          randomNumberGenerator = new Random();
      }
    return randomNumberGenerator.nextDouble();
  }
  //...

Galileo Computing

10.5.4 Probleme mit komplexen Zahlen  downtop

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.

gp  Es gibt keine implizite Typumwandlung von double nach Complex.
gp  Es gibt keine Werte-Semantik bei Zuweisung und Gleichheit.
gp  Es wird ein zeitaufwendiger Konstruktor zur Erschaffung benötigt.
gp  Es sind keine normalen Operatoren erlaubt.

Galileo Computing

10.6 System  downtop

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.

gp  Die Standard-Ein- bzw. Ausgabe wird mit Hilfe der static final-Stream-Variablen in, out bzw. err (für Fehler) vorgenommen.
gp  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.
gp  Es gibt Methoden zum Laufzeitsystem, die an die Klasse Runtime delegiert werden, wie z.B. der Programmabbruch durch exit().
gp  Man hat Zugriff auf den Sicherheitsmanager SecurityManager. Der Sicherheitsmanager prüft, ob Operationen erlaubt sind.
gp  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.


Galileo Computing

10.6.1 Byte-orientierte Konsol-Ausgabe  downtop

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

gp  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.


Galileo Computing

10.6.2 Zeichen-Codierung und Unicode  downtop

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.

gp  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

gp  UnicodeBig: zwei Byte Startmarkierung, big-endian Zeichenformat
gp  UnicodeSmall: zwei Byte Startmarkierung, little-endian Format
gp  UnicodeBigUnmarked: unmarkiertes UnicodeBig
gp  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.


Galileo Computing

10.6.3 Zeichen- vs. Byte-Streams  downtop

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


Galileo Computing

10.6.4 Default-Codierung und System.out  downtop

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

gp  unterliegen der Default-Codierung
gp  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
Abbildung 10.3   Zeichen-Ausgabe mittels System.out

Zur Wirkung der Methoden:

write() und flush()

gp  Die Methode write() ignoriert das obere Byte, die Methode print() gibt dagegen alle Zeichen oberhalb von \u00FF als ? aus.
gp  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.
gp  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.


Galileo Computing

10.6.5 Byte-orientierte Eingabe mit System.in  downtop

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

gp  als Instanz von FileInputStream instanziert und
gp  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:

gp  read() liest ein oder mehrere Zeichen von der Standard-Tastatur in einen internen Puffer.
gp  Erst bei Eingabe des Zeilenende-Zeichens '\n' bzw. \u000A gibt read() die eingelesenen Zeichen (inklusive des Zeilenendes) als einzelne int-Werte aus.
gp  Alle eingegebenen Zeichen (außer dem Zeilenende) erscheinen auf der Konsole sofort als »Echo« und können nicht unterdrückt werden.
gp  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.


Galileo Computing

10.6.6 System-Properties  downtop

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

gp  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.

gp  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).


Galileo Computing

10.6.7 Methode: load() bzw. loadLibrary()  downtop

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"); 
  }

Galileo Computing

10.6.8 Methode: identityHashCode()  downtop

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.


Galileo Computing

10.6.9 Methode: currentTimeMillis()  downtop

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

Galileo Computing

10.6.10 Methode: arraycopy()  downtop

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

Galileo Computing

10.6.11 Methode: exit()  downtop

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.


Galileo Computing

10.7 Process  downtop

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 

gp  In der Regel sind damit Prozesse wieder auf Java-Apps, interaktive Angaben bzw. Konfigurationsdateien beschränkt.

Methoden in Process

Process hat nur wenige Methoden:

gp  waitFor() suspendiert den Aufrufer so lange, bis der Prozess endet.16 
gp  destroy() zerstört den Prozess.
gp  exitValue() liefert den Rückgabe-Code des beendeten Prozesses.17 
gp  getInputStream() bzw. getErrorStream() sind zwei Streams, von denen Bytes gelesen werden können, die der Prozess zur Standard-Ausgabe bzw. -Fehlerausgabe sendet.
gp  getOutputStream() sendet Bytes zur Standard-Eingabe des Prozesses.

Ein Beispiel hierzu ist im nächsten Abschnitt aufgeführt.


Galileo Computing

10.8 Runtime  downtop

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.


Galileo Computing

10.8.1 Methode: exec()  downtop

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); }
  }
}
Hallo
Welt
0

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.


Galileo Computing

10.9 Zusammenfassung  downtop

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.


Galileo Computing

10.10 Testfragen  toptop

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.

  

Perl – Der Einstieg




Copyright © Galileo Press GmbH 2001 - 2002
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken und speichern. Ansonsten unterliegt das <openbook> denselben Bestimmungen wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.
Die Veröffentlichung der Inhalte oder Teilen davon bedarf der ausdrücklichen schriftlichen Genehmigung von Galileo Press. Falls Sie Interesse daran haben sollten, die Inhalte auf Ihrer Website oder einer CD anzubieten, melden Sie sich bitte bei: stefan.krumbiegel@galileo-press.de


[Galileo Computing]

Galileo Press GmbH, Gartenstraße 24, 53229 Bonn, fon: 0228.42150.0, fax 0228.42150.77, info@galileo-press.de