Java SE Embedded Compact Profiles

Hat ein System beschränkten Speicher, wird es knapp für das gesamte Java SE. Daher gibt es Teilmengen der Java SE, genannt Profiles, die nur knapp über 11 MiB benötigen. Im Moment gibt es drei Profile, also drei Teilmengen der Java SE, die jeweils immer etwas mehr können. compact1 ist die kleinste Teilmenge, wo zum Beispiel die Kernklassen enthalten sind, compact2 nimmt die API für Datenbankverbindungen mit auf, compact3 kann dann XML-Verarbeitung und die komplette Java SE grafische Oberflächen darstellen oder Web-Services deklarieren.

Jede Klasse ist einem Profil zugeordnet, was der Compiler statisch prüfen kann – solange der Klassenname nicht dynamisch bestimmt wird. So lässt sich sicherstellen, dass nur Typen referenziert werden, die auch in einem Profil sind.

Oracle Java Standard Edition Embedded (Oracle Java SE Embedded)

Oracle unterstützt mit der Oracle Java Standard Edition Embedded (Oracle Java SE Embedded)[1] Profile. Allerdings laden Entwickler keine einfache Laufzeiumgebung herunter, sondern ein Werkzeug JRECreate, mit dem eine Laufzeitumgebung mit unseren eigenen Paketen für ein bestimmtes System generiert wird. Drei Laufzeitumgebungen sind wählbar: eine Minimal-JVM, Client JVM (schnelle Reaktionsfähigkeit), Server JVM (verbraucht mehr Hauptspeicher). Sehr fein kann dann bestimmt werden, ob JavaFX mit dabei sein soll, oder welche Sprachübersetzungen mit ausgeliefert werden. Am Ende steht ein Verzeichnis mit bin und JRE, lib und Java-Archiven, Konfigurationen und – das darf bei Oracle auf keinen Fall fehlen – Copyright- und Lizenz-Dokumenten.

[1]      http://www.oracle.com/technetwork/java/embedded/embedded-se/downloads/index.html

Boolean nach Ganzzahl konvertieren

Der primitive Typ boolean lässt sich nicht über eine Typumwandlung in einen anderen primitiven Typ konvertieren. Doch in der Praxis kommt es vor, dass true auf 1 und false auf 0 abgebildet werden muss; der übliche Weg ist:

int val = aBoolean ? 1 : 0;

Exotischer ist:

int val = Boolean.compare( aBoolean, false );

Noch exotischer folgendes:

int val = 1 & Boolean.hashCode( true ) >> 1;

Nachträgliches Implementieren von Schnittstellen

Implementiert eine Klasse eine bestimmte Schnittstelle nicht, so kann sich auch nicht am dynamischen Binden über diese Schnittstelle teilnehmen, auch wenn sie eine Methoden hat, über die eine Schnittstelle abstrahiert. Besitzt zum Beispiel die nicht-finale Klasse FIFA eine öffentliche Methode price(), implementiert aber Buyable mit einer gleich benannten Methoden nicht, so lässt sich zu einem Trick greifen, sodass eine Implementierung geschaffen wird, die die existierende Methode aus der Klasse und die der Schnittstelle in die Typhierarchie bringt.

class FIFA {
  public double price() { … }
}

interface Buyable {
   double price();
 }

class FIFAisBuyable extends FIFA implements Buyable { }

Eine neue Unterklasse FIFAisBuyable erbt von der Klasse FIFA und implementiert die Schnittstelle Buyable, sodass der Compiler die existierende price()-Methode mit Vorgabe der Schnittstelle vereinigt. Nun lässt sich FIFAisBuyable als Buyable nutzen und dahinter steckt die Implementierung von FIFA. Als Unterklasse bleiben auch alle sichtbaren Eigenschaften der Oberklasse erhalten.

Setzen des Java Klassenpfades

Wo die JVM die Klassen findet muss ihr mitgeteilt werden, und das ist in der Praxis elementar für die Auslieferung, auch englisch Deloyment genannt. Java wartet mit dem Laden der Klassen so lange, bis sie benötigt werden. Es gibt zum Beispiel Programmabläufe nur zu besonderen Bedingungen und wenn dann erst spät ein neuer Typ referenziert wird, der nicht vorhanden ist, fällt dieser Fehler erst sehr spät auf. Der JVM müssen folglich nicht nur die Quellen für Klassen und Ressourcen der eigenen Applikation mitgeteilt werden, sondern alle vom Programm referenzierten Typen aus zum Beispiel quelloffenen und kommerziellen Bibliotheken.

Sollen in einem Java-Projekt Dateien aus einem Verzeichnis oder einem externen Java-Archiv geholt werden, so ist der übliche Weg, diese Verzeichnisse oder Archive in einem Klassenpfad anzugeben. Diese Angabe ist für alle SDK-Werkzeuge notwendig – am Häufigsten ist sie beim Compiler und bei der Laufzeitumgebung zu sehen.

Schalter -classpath

Die Suchorte lassen sich flexibel angeben, wobei die erste Variante einem SDK-Werkzeug über den Schalter -classpath (kurz -cp) die Klassendateien bzw. Archive liefert:

$ java -classpath classpath1;classpath2 MainClass

Der Klassenpfad enthält Wurzelverzeichnisse der Pakete und JAR-Dateien, also Archive von Klassendateien und Ressourcen.

Beispiel: Nimm ein Java-Archiv library.jar im aktuellen Verzeichnis, die Ressourcen unter dem bin-Verzeichnis und alle JAR-Dateien im Verzeichnis lib in den Klassenpfad mit auf:

$ java -cp "library.jar;bin/.;lib/*" com.tutego.MainClass

Unter Windows ist der Trenner ein Semikolon, unter Unix ein Doppelpunkt! Das Sternchen steht für alle JAR-Dateien, es ist kein üblicher Wildcard, wie z.B. parser*.jar.[1] Sehen Kommandozeilen der Betriebssysteme ein *, beginnen sie in der Regel eine eigenen Verarbeitung; daher muss die gesamte Pfadangabe in doppelten Anführungszeichen stehen.

Umgebungsvariable CLASSPATH

Eine Alternative zum Schalter -cp ist das Setzen der Umgebungsvariablen CLASSPATH mit einer Zeichenfolge, die Pfadangaben spezifiziert:

$ SET CLASSPATH=classpath1;classpath2
$ java MainClass

Problematisch ist der globale Charakter der Variablen, sodass lokale -cp-Angaben besser sind. Außerdem „überschreiben“ die -cp-Optionen die Einträge in CLASSPATH. Zu guter Letzt: ist weder CLASSPATH noch eine -cp-Option gesetzt, besteht der Klassenpfad für die JVM nur aus dem aktuellen Verzeichnis, also „.“.

[1]      Weitere Details unter https://docs.oracle.com/javase/8/docs/technotes/tools/windows/classpath.html.

Klassenlader (Class Loader) und Klassenpfad

Ein Klassenlader ist dafür verantwortlich, eine Klasse zu laden. Aus der Datenquelle (im Allgemeinen einer Datei) liefert der Klassenlader ein Byte-Array mit den Informationen, die im zweiten Schritt dazu verwendet werden, die Klasse ins Laufzeitsystem einzubringen; das ist Linking. Es gibt vordefinierte Klassenlader und die Möglichkeit, eigene Klassenlader zu schreiben, um etwa verschlüsselte und komprimierte .class-Dateien aus Datenbanken zu laden.

Klassenladen auf Abruf

Nehmen wir zu Beginn ein einfaches Programm mit zwei Klassen:

class Person {
   static String NOW = java.time.LocalDateTime.now().toString();
   public static void main( String[] args ) {
     Dog wuffi = new Dog();
   }
 }
 
 class Dog {
   Person master;
 }

Wenn die Laufzeitumgebung das Programm Person startet, muss sie eine Reihe von Klassen laden. Das tut sie dynamisch zur Laufzeit. Sofort wird klar, dass es zumindest Person sein muss. Wenn aber die statische main(String[])-Methode aufgerufen wird, muss auch Dog geladen sein. Und da beim Laden einer Klasse auch die statischen Variablen initialisiert werden, wird auch die Klasse LocalDateTime geladen.

Zwei weitere Dinge werden nach einiger Überlegung deutlich:

  • Wenn Dog geladen wird, bezieht es sich auf Person. Da Person aber schon geladen ist, muss es nicht noch einmal geladen werden.
  • Unsichtbar stecken noch andere referenzierte Klassen dahinter, die nicht direkt sichtbar sind. So wird zum Beispiel Object geladen, da implizit in der Klassendeklaration von Person steht: class Person extends Object. Auch String muss geladen werden, weil String einmal in der Signatur von main(String[]) vorkommt und es der Typ von now Intern ziehen die Typen viele weitere Typen nach sich. String implementiert Serializable, CharSequence und Comparable, also müssen diese drei Schnittstellen auch geladen werden. Und so geht das weiter, je nach dem, welche Programmpfade abgelaufen werden. Wichtig ist aber zu verstehen, dass diese Klassendateien so spät wie möglich geladen werden.

Im Beispiel mit den Klassen Person und Dog lädt die Laufzeitumgebung selbstständig die Klassen (implizites Klassenladen). Klassen lassen sich auch mit Class.forName(String) über ihren Namen laden (explizites Klassenladen).

Hinweis. Um zu sehen, welche Klassen überhaupt geladen werden, lässt sich der virtuellen Maschine beim Start der Laufzeitumgebung ein Schalter mitgeben -verbose:class. Dann gibt die Maschine beim Lauf alle Klassen aus, die sie lädt.

JAR-Dateien

Große Sammlungen von Java-Klassendatein und Ressourcen werden in sogenannten Java-Archiven, kurz JAR-Dateien, zusammengefasst. Diese Dateien sind im Grunde ganz normale ZIP-Archive mit einem besonderen Verzeichnis META-INF für Meta-Dateien. Das JDK bringt im bin-Verzeichnis das Werkzeug jar zum Aufbau und Extrahieren von JAR-Dateien mit.

JAR-Dateien behandelt die Laufzeitumgebung wie Verzeichnisse von Klassendateien und Ressourcen. Wenn Java-Software ausgeliefert wird, dann bieten sich JAR-Dateien an, denn es ist einfacher, nur ein komprimiertes Archiv weiterzugehen als einen großen Dateibaum. Zudem haben Java-Archive den Vorteil, dass sie signiert werden können und illegale Änderungen auffallen.

Woher die kleinen Klassen kommen: Die Suchorte und spezielle Klassenlader

Die Laufzeitumgebung nutzt zum Laden nicht nur einen Klassenlader, sondern mehrere. Die Java-Laufzeitumgebung nutzt diese verschiedenen Klassenlader um unterschiedliche Orte festzulegen. Ein festes Schema bestimmt die Suche nach den Klassen:

  1. Klassen Typen wie String, Object oder Point stehen in einem ganz speziellen Archiv. Wenn ein eigenes Java-Programm gestartet wird, so sucht die virtuelle Maschine die angeforderten Klassen zuerst in diesem Archiv. Da es elementare Klassen sind, die zum Hochfahren eines Systems gehören, werden sie Bootstrap-Klassen Das Archiv mit diesen Klassen heißt oft rt.jar (für Runtime). Andere Archive können hinzukommen – wie i18n.jar, das Internationalisierungsdaten beinhaltet. Die Implementierung dieses Bootstrap-Klassenlader ist nicht öffentlich und wird von System zu System unterschiedlich sein. Ab Java 9 wird sich das grundlegend ändern.
  2. Ist eine Klasse keine Bootstrap-Klasse, beginnt der System-Klassenlader Applikations-Klassenlader die Suche im Klassenpfad (Classpath). Diese Pfadangabe besteht aus einer Aufzählung einzelner Klassendateien, Verzeichnisse, Klassen oder JAR-Archive, in denen die Laufzeitumgebung nach den Klassendateien sucht. Standardmäßig ist dieser Klassenpfad auf das aktuelle Verzeichnis gesetzt („.“), er lässt sich aber beliebig setzen.

Zusätzliche statische Initialisier im enum

Es sind Blöcke der Art static { … } im Rumpf eines Aufzählungstyps erlaubt. Lädt die Laufzeitumgebung einer Klasse, initialisiert sie der Reihe nach alle statischen Variablen bzw. führt die static-Blöcke aus. Die Aufzählungen sind statische Variablen und werden beim Laden initialisiert. Steht der statische Initialisierer hinter den Konstanten, so wird auch er später aufgerufen als die Konstuktoren, die vielleicht auf statische Variablen zurückgreifen wollen, die der static-Block initialisiert. Ein Beispiel:

public enum Country {
  GERMANY, UK, CHINA;
  {
    System.out.println( "Objektinitialisierer" );
  }
  static {
    System.out.println( "Klasseninitialisier" );
  }

  private Country() {
    System.out.println( "Konstruktor" );
  }

  public static void main( String[] args ) {
    System.out.println( GERMANY );
  }
}

Die Ausgabe ist:

Objektinitialisierer

Konstruktor

Objektinitialisierer

Konstruktor

Objektinitialisierer

Konstruktor

Klasseninitialisier

GERMANY

Die Ausführung und Ausgabe hängt von der Reihenfolge der Deklaration ab und jede Umsortierung führt zu einer Verhaltensänderung. Jetzt könnten Programmierer auf die Idee kommen, mögliche static-Blöcke an den Anfang zu setzen, vor die Konstanten. Meine Leser sollten das Ergebnis testen …

Inselupdate: Suchen und Ersetzen mit Mustern

Für den Suchfall mit Java RegEx gibt es noch eine Erweiterung, dass nämlich die Pattern-Klasse die Fundstellen nicht nur ermittelt, sondern sie auch durch etwas anderes ersetzen kann.

Beispiel: In einem String sollen alle Nicht-JVM-Sprachen ausgepiept werden:

String  text    = „Ich mag Java, Groovy und ObjectiveC und PHP.“;
Matcher matcher = Pattern.compile(„ObjectiveC|PHP“ ).matcher( text );
StringBuffer sb = new StringBuffer();
while ( matcher.find() )
matcher.appendReplacement( sb, „[PIEP]“ );
matcher.appendTail( sb );
System.out.println( sb );  // Ich mag Java, Groovy und [PIEP] und [PIEP].

Um mit dem Mechanismus „Suchen und Ersetzen“ zu arbeiten, wird zunächst ein Container vom Typ StringBuffer aufgebaut, denn in dem echten String kann Pattern die Fundstellen nicht ersetzen. (Leider ist ein StringBuffer nötig, die API akzeptiert keinen StringBuilder.) Erkennt der Matcher ein Muster, macht appendReplacement(…) zwei Dinge:

1. Die Methode füllt den Container mit allen Zeichen vom letzten Fund bis zur jetzigen Fundstelle auf. Beim ersten Aufruf ist das „Ich mag Java, Groovy und “, dann folgt „ und “.

2. In den StringBuffer können wir unsere Ersetzung, in diesem Falle „[PIEP]“, setzen.

So wächst der StringBuffer von Schritt zu Schritt. Nach der letzten Fundstelle setzt appendTail(…) das noch verbleibende Teilstück von der letzten Funstelle bis zum Stringende in den StringBuffer-Container.

Im Prinzip können wir in der while-Schleife mit matcher.group(…) auf das Fundstück zurückgreifen und es in die Ersetzung einbauen. Doch toll an appendReplacement(…) ist, dass der Ersetzungsstring ein $ enthalten darf – mit dem Problem, dass ein vorkommendes Dollar-Zeichen ausmaskiert werden muss –, der Zugriff auf die Suchgruppe bietet. Damit lassen sich sehr elegante Lösungen bauen. Nehmen wir an, wir müssen in einer Zeichenkette alle URLs in HTML-Hyperlinks konvertieren. Dann rahmen wir einfach jede Fundstelle in die nötigen HTML-Tags ein. In Quellcode sieht das so aus:

Listing 2.7: RegExSearchAndReplace.java, main()

String  text    = „Hi, schau mal bei http://stackoverflow.com/ “ +
                  „oder http://www.tutego.de/ vorbei.“;
String  regex   = „http://[a-zA-Z0-9\\\]+\\.[a-zA-Z]{2,3}(\\S*)?“;
Matcher matcher = Pattern.compile( regex ).matcher( text );
StringBuffer sb = new StringBuffer( text.length() );
while ( matcher.find() )
  matcher.appendReplacement( sb, „<a href=\“$0\“>$0</a>“ );
matcher.appendTail( sb );
System.out.println( sb );

Der StringBuffer enthält dann zum Schluss „Hi, schau mal bei <a href=“http://stackoverflow.com/“>http://stackoverflow.com/</a> oder <a href=“http://www.tutego.de/“>http://www.tutego.de/</a> vorbei.“. (Der gewählte reguläre Ausdruck für URLs ist kurz, aber nicht vollständig. Für das Beispiel spielt das aber keine Rolle.)

Tipp: Die String-Methoden replaceAll(…) und replaceFirst(…) ersetzen direkt, und arbeiten im Hintergrund genauso. Zum Einsatz kommt die replaceAll(.,.)-Methode vom Matcher.

Hinweis: Der Ersetzungsausdruck „<a href=\“$0\“>$0</a>“ enthält mit $ Steuerzeichen für den Matcher. Wenn die Ersetzung aber überhaupt nicht mit $n auf das gefundene Wort zurückgreift, sollten die beiden Sonderzeichen \ und $ ausmaskiert werden. Auf diese Weise werden merkwürdige Fehler vermieden, wenn doch in der Ersetzung ein Dollar-Zeichen oder ein Backslash vorkommt. Das Ausmaskieren übernimmt die Methode quoteReplacement(…), sodass sich zum Beispiel Folgendes ergibt:

matcher.appendReplacement( sb, Matcher.quoteReplacement( replacement ) );

Lokale Klassen und effektiv finale Variablen

Im folgenden Beispiel deklariert die main(…)-Methode eine innere Klasse Snowden mit einem Konstruktor, der auf die finale Variable PRISM zugreift:

public class NSA {

public static void main( String[] args ) {

final int PRISM = 1;

int tempora = 2;

tempora++; // (*)

class Snowden {

Snowden() {

System.out.println( PRISM );

// System.out.println( tempora ); // Auskommentiert ein Compilerfehler

}

}

new Snowden();

}

}

Die Deklaration der inneren Klasse Snowden wird hier wie eine Anweisung eingesetzt. Ein Sichtbarkeitsmodifizierer ist bei inneren lokalen Klassen ungültig, und die Klasse darf keine Klassenmethoden und allgemeinen statischen Variablen deklarieren (finale Konstanten schon).

Jede lokale Klasse kann auf Methoden der äußeren Klasse zugreifen und zusätzlich auf die lokalen Variablen und Parameter, die final sind. Das ist die Variable PRISM auf jeden Fall, tempora ist nicht final (tempora++ ist ein Schreibzugriff), und daher führt eine Konsolenausgabe mit println(tempora) auch zu einem Compilerfehler – Eclipse meldet: „Local variable tempora defined in an enclosing scope must be final or effectively final”. Der letzte Teil der Fehlermeldung gibt einen Hinweis auf eine kleine Änderung seit Java 8, dass Variablen nicht zwingend mit dem Modifizierer final ausgezeichnet werden müssen, um final zu sein. Gibt es keinen Schreibzugriff auf Variablen, sind sie effektiv final. Im Beispiel kann das einfach getestet werden: Wird die Zeile (*) mit tempora++; auskommentiert, so ist tempora effektiv final und Snowden kann auf tempora zugreifen.

Liegt die innere Klasse in einer statischen Methode, kann sie keine Objektmethoden der äußeren Klasse aufrufen.

Varargs-Design-Tipps

  • Hat eine Methode nur einen Array-Parameter, und steht der noch am Ende, so kann dieser relativ einfach durch ein Vararg ersetzt werden. Das gibt dem Aufrufer die komfortable Möglichkeit, eine kompaktere Syntax zu nutzen. Unsere main(String[] args)-Methode kann auch als main(String… args) deklariert werden, sodass der main(…)-Methode bei Tests einfach variable Argumente übergeben werden können.
  • Muss eine Mindestanzahl von Argumenten garantiert werden – bei max(…) sollten das mindestens zwei sein –, ist es besser, eine Deklaration wie folgt zu nutzen: max(int first, int second, int… remaining).
  • Aus Performance-Gründen ist es nicht schlecht, Methoden mit häufigen Parameterlistengrößen als feste Methoden anzubieten, etwa max(double, double), max(double, double, double) und dann max(double…). Der Compiler wählt automatisch immer die passende Methode aus und für zwei oder drei Parameter sind keine temporären Feld-Objekte nötig und die automatische Speicherbereinigung muss nichts wegräumen.

Inselupdate: Wie wo was dynamisch binden

Es gibt bei Methoden von konkreten Klasse, abstrakte Klassen und Schnittstellen Unterschiede, wo der Aufruf letztendlich landet. Nehmen wir folgende Methode an:

void f( T t ) {
  t.m();
}

Fordert die Methode ein Argument vom Typ T und ruft auf dem Parameter t die Methode m() auf, so können wir folgendes festhalten:

· Ist T eine finale Klasse, so wird immer die Methode m() von T aufgerufen, da es keine Unterklassen geben kann, die m() überschreiben.

· Ist T eine nicht-finale Klasse und m() eine finale Methode, wird genau m() aufgerufen, weil kein Unterklasse m() überschreiben kann.

· Ist T eine nicht-finale Klasse und m() keine finale Methode, so könnten Unterklassen von T m() überschreiben und t.m() würde dann dynamisch die überschriebene Methode aufrufen.

· Ist T eine abstrakte Klasse und m() eine abstrakte Methode, so wird in jedem Fall eine Realisierung von m() in einer Unterklasse aufgerufen.

· Ist T eine Schnittstelle, und m() keine Default-Implementierung, so wird in jedem Fall eine Implementierung m() einer implementierenden Klasse aufgerufen.

· Ist T eine Schnittstelle, und m() eine Default-Implementierung, so kann t.m() bei der Default-Implementierung landen, oder bei einer überschriebenen Version einer implementierenden Klasse.

Doppelklammer-Initialisierung

Da anonyme Klassen keinen Namen haben, muss für Konstruktoren ein anderer Weg gefunden werden. Hier helfen Exemplarinitialisierungsblöcke, also Blöcke in geschweiften Klammern.

Exemplarinitialisierer gibt es ja eigentlich gar nicht im Bytecode, sondern der Compiler setzt den Programmcode automatisch in jeden Konstruktor. Obwohl anonyme Klassen keinen direkten Konstruktor haben können, gelangt doch über den Exemplarinitialisierer Programmcode in den Konstruktor der Bytecode-Datei.

Dazu ein Beispiel: Die anonyme Klasse ist eine Unterklasse von Point und initialisiert im Konstruktor einen Punkt mit Zufallskoordinaten. Aus diesem speziellen Punkt-Objekt lesen wir dann die Koordinaten wieder aus:

java.awt.Point p = new java.awt.Point() {

{

x = (int)(Math.random() * 1000); y = (int)(Math.random() * 1000);

}

};

System.out.println( p.getLocation() ); // java.awt.Point[…

System.out.println( new java.awt.Point( -1, 0 ) {{

y = (int)(Math.random() * 1000);

}}.getLocation() ); // java.awt.Point[x=-1,y=…]

Sprachlichkeit

Wegen der beiden geschweiften Klammen heißt diese Variante auch Doppelklammer-Initialisierung (engl. Double Brace Initialization).

Die Doppelklammer-Initialisierung ist kompakt, wenn etwas Datenstrukturen oder hierarchische Objekte initialisiert werden sollen.

Beispiel *

Im Folgenden Beispiel erwartet appendText(…) ein Objekt vom Typ HashMap, was durch den Trick direkt initialisiert wird:

String s = new DateTimeFormatterBuilder()

.appendText( ChronoField.AMPM_OF_DAY,

new HashMap<Long, String>() {{ put(0L, „früh“);put(1L,“spät“ ); }} )

.toFormatter().format( LocalTime.now() );

System.out.println( s );

Im nächsten Beispiel bauen wir eine geschachtelte Map, das ist ein Assoziativspeicher, der wieder einen anderen Assoziativspeicher enthält:

Map<String,Object> map = new HashMap<String,Object>() {{

put( „name“, „Chris“ );

put( „address“, new HashMap<String,Object>() {{

put( „street“, „Feenallee 1“ );

put( „city“, „Elefenberg“ );

}} );

}};

Warnung

Die Doppelklammerinitialisierung ist nicht ganz „billig“, da eine Unterklasse aufgebaut wird, also neuer Bytecode generiert wird. Zudem hält die innere Klasse eine Referenz auf die äußere Klasse fest. Des Weiteren kann es Probleme mit equals(…) geben, da wir mit der Doppelklammerinitialisierung eine Unterklasse schaffen, die vielleicht mit equals(…) nicht mehr gültig vergleichen werden kann, denn die Class-Objekte sind jetzt nicht mehr identisch. Das spricht in der Summe eher gegen diese Konstruktion.

Sich selbst mit this übergeben

Möchte sich ein Objekt A einem anderen Objekt B mitteilen, damit B das andere Objekt A „kennt“, so funktioniert das gut mit der this-Referenz. Demonstrieren wir das an einem „Ich bin dein Vater“-Beispiel: Zwei Klasen Luke und Darth repräsentieren zwei Personen, wobei Luke ein Attribut dad für seinen Vater hat:

LukeAndDarth.java, Teil 1
class Luke {
  Darth dad;
}

class Darth {
  void revealTruthTo( Luke son ) {
    son.dad = this;
  }
}

Spannend ist die Methode revealTruthTo(Luke), denn sie setzt beim übergebenen Luke-Objekt das dad-Attribut mit der this-Referenz. Damit kennt Luke seinen Vater, getestet in folgender Klasse:

LukeAndDarth.java, Teil 2
public class LukeAndDarth {
  public static void main( String[] args ) {
    Luke luke = new Luke();
    Darth darth = new Darth();
    System.out.println( luke.dad ); // null
    darth.revealTruthTo( luke );
    System.out.println( luke.dad ); // Darth@15db9742
  }
}

Hinweis: In statischen Methoden steht die this-Referenz nicht zur Verführung, da wir uns in Klassenmethoden nicht auf ein konkretes Objekt beziehen.

toString() für equals-Vergleiche?

Einige kreative Programmierer nutzen die toString()-Repräsentation für Objektvergleiche. Etwas der Richtung: Wenn wir zwei Point-Objekte p und q haben, und p.toString().equals(q.toString()) ist, dann sind beide Punkte eben gleich. Doch ist es hochgradig gefährlich sich auf die Rückgabe von toString() zu verlassen aus mehreren Gründen: Offensichtlich ist, dass toString() nicht unbedingt überschrieben sein muss. Zweitens muss toString() nicht unbedingt alle Elemente repräsentieren und die Ausgabe könnte abgekürzt sein. Drittens können natürlich Objekte equals-gleich sein, auch wenn ihre String-Repräsentation nicht gleich ist, was etwa bei URL-Objekten der Fall ist. Der einzige erlaubte Fall für so eine Konstruktion wäre String/StringBuilder/StringBuffer/CharSequence, wo es ausdrücklich um Zeichenketten geht.

Habe ich Gründe vergessen?

Verwandtschaft von Methode und Konstruktor

Methoden und Konstruktoren besitzen beide Programmcode, haben eine Parameterliste, Modifizierer, können auf Objektvariablen zugreifen und this verwenden – das sind ihre Gemeinsamkeiten. Ein schon erwähnter Unterschied ist, dass Methoden einen Rückgabetyp besitzen (auch wenn er nur void ist), Konstruktoren aber nicht. Zwei weitere Unterschiede betreffen die Syntax und Semantik.

Konstruktoren tragen immer den Namen ihrer Klasse, und da Klassennamen per Konvention großgeschrieben werden, sind auch Konstruktoren immer großgeschrieben – Methoden werden in der Regel immer kleingeschrieben. Und Methoden sind in der Regel Verben, die das Objekt anweisen etwas zu tun, Klassennamen sind Nomen und keine Verben.

Der Programmcode eines Konstruktors wird automatisch nach dem Erzeugen eines Objekts von der JVM genau einmal aufgerufen, und zwar als erstes vor allen anderen Methoden. Methoden lassen sich beliebig oft aufrufen und unterliegen der Kontrolle des Benutzers. Konstruktoren lassen sich später nicht noch einmal auf einem schon existierenden Objekt erneut aufrufen und so ein Objekt reinitialisieren. Der Konstruktor-Aufruf ist implizit und automatisch mit new verbunden und kann nicht getrennt vom new gesehen werden.

Zusammenfassend können wir sagen, dass ein Konstruktor eine Art spezielle Methode zur Initialisierung eines Objektes ist.

JVM-Interna: „Ein Java-Compiler setzt Konstruktoren als void-Methoden um, die „<init>“ heißen“.

“Java ist auch eine Insel”, 11. Auflage, jetzt neu im Handel

Cover von Java ist auch eine InselNach mehr als zwei Jahren gibt es ein Update der Insel: https://www.galileo-press.de/java-ist-auch-eine-insel_3606/. Aktualisiert auf Java 8, 1306 Seiten, ISBN 978-3-8362-2873-2, E-Book-Formate: PDF, EPUB, MOBI, Online.

Online ist das Buch nicht mehr, da es zu juristischen Auseinandersetzungen mit Mitbewerben kommen könnte. Vereinfacht ausgedrückt: Etwas verkaufen, was gleichzeitig frei ist, macht den Verlag verletzbar gegenüber Abmahnungen. Online bleibt aber weiterhin die jeweils letzte Version, also komplett wie üblich Auflage 10: http://www.tutego.de/javabuch/; das gilt auch für den 2. Band, den es ebenfalls bald im Handel geben wird.

Weiterhin ist eine PDF mit Kapitel 2 und 3 mit nahezu 300 Seiten vom 1. Band, 11. Auflage, online.

Inselraus: JDBC Metadaten

Von einer Datenbank können verschiedene Informationen ausgelesen werden. Zum einen sind dies Informationen zu einer bestimmten Tabelle, zum anderen Informationen über die Datenbank selbst.

Metadaten über die Tabelle

Bei der Abfrage über alle Spalten müssen wir die Struktur der Datenbank kennen, insbesondere dann, wenn wir allgemeine Abfragen vornehmen und die passenden Daten herauslesen wollen. So liefert SELECT * FROM Item ein ResultSet mit der Anzahl der Spalten, wie sie die Tabelle Item hat. Doch bevor wir nicht die Anzahl und die Art der Spalten kennen, können wir nicht auf die Daten zugreifen.

Um diese Art von Informationen, so genannte Metadaten, in Erfahrung zu bringen, befindet sich die Klasse ResultSetMetaData, mit der wir diese Informationen erhalten, unter den SQL-Klassen. Metadaten können für jede Abfrage angefordert werden. So lässt sich unter anderem leicht herausfinden:

  • wie viele Spalten wir in einer Zeile abfragen können
  • wie der Name der Spalte lautet
  • welchen SQL-Typ die Spalte hat
  • ob NULL für eine Spalte in Ordnung ist
  • wie viele Dezimalzeichen eine Spalte hat

Einige Informationen über die Bestellelemente

Um Anzahl und Art der Spalten einer Bestelltabelle herauszufinden, werden wir zunächst ein ResultSet mit stmt.executeQuery(„SELECT * FROM Item“) erzeugen und dann via getMetaData() ein ResultSetMetaData-Objekt erfragen. Das ResultSetMetaData-Objekt besitzt viele Methoden, um Aussagen über die Tabelle und die Spalten zu treffen. So fragen wir mit getColumnCount() nach, wie viele Spalten die Tabelle hat. Anschließend lässt sich für jede Spalte der Name und Typ erfragen:

String url = "jdbc:hsqldb:file:TutegoDB;shutdown=true";
String sql = "SELECT * FROM ITEM";
try ( Connection con = DriverManager.getConnection( url, "sa", "" );
      Statement stmt = con.createStatement();
      ResultSet rs = con.createStatement().executeQuery( sql ) ) {
  ResultSetMetaData meta = rs.getMetaData();

  int numerics = 0;

  for ( int i = 1; i <= meta.getColumnCount(); i++ ) {
    System.out.printf( "%-20s %-20s%n", meta.getColumnLabel( i ),
                                        meta.getColumnTypeName( i ) );
    if ( meta.isSigned( i ) )
      numerics++;
  }

  System.out.println();
  System.out.println( "Spalten: " + meta.getColumnCount() +
                      ", Numerisch: " + numerics );
}

interface java.sql.ResultSet
extends Wrapper, AutoCloseable

  • ResultSetMetaDatagetMetaData()throwsSQLException
    Liefert die Eigenschaften eines ResultSet in einem ResultSetMetaData zurück.

interface java.sql.ResultSetMetaData
extends Wrapper

  • intgetColumnCount()
    Liefert die Anzahl der Spalten im aktuellen ResultSet. Das ist praktisch für SQL-Anweisungen wie SELECT *.

Allen folgenden Methoden wird ein int übergeben, das die Spalte kennzeichnet:

  • StringgetCatalogName(intcolumn)
    Gibt den String mit dem Katalognamen der Tabelle für die angegebene Spalte zurück.
  • StringgetColumnName(intcolumn)
    Liefert den Spaltennamen der Tabelle.
  • intgetColumnDisplaySize(intcolumn)
    Maximale Anzahl der Zeichen, die die Spalte einnimmt. So ist bei einer Spalte vom Typ VARCHAR(11) mit einer maximalen Spaltenbreite von zehn Zeichen zu rechnen. Bei numerischen Spalten variiert der Wert.
  • StringgetColumnLabel(intcolumn)
    Gibt einen String zurück, der den Titel der angegebenen Spalte enthält. Der Titel gibt an, welche Überschrift für die Spalte angezeigt werden soll. Einige Datenbanken erlauben die Unterscheidung zwischen Spaltennamen und Spaltentitel.
  • intgetColumnType(intcolumn)
    Der Typ der Spalte wird ermittelt. Der Spaltentyp ist dabei eine Konstante aus der Klasse java.sql.Types. Sie deklariert Konstanten nach dem XOPEN-Standard. Dazu zählen unter anderem BIGINT, BINARY, BIT, BLOB_LOCATOR, CHAR, CLOB_LOCATOR, DATE, DECIMAL, DISTINCT, DOUBLE, FLOAT, INTEGER, JAVA_OBJECT (benutzerdefinierter Datentyp), STRUCT, TIME, TIMESTAMP, TINYINT, VARBINARY, VARCHAR. Die Konstante OTHER zeigt ein datenbankspezifisches Element an und wird auf ein Java-Objekt abgebildet, falls ein Zugriff mittels getObject(…) oder setObject(…) erfolgt.
  • StringgetColumnTypeName(intcolumn)
    Liefert den Namen der Spalte, so wie sie die Datenbank definiert.
  • intgetPrecision(intcolumn)
    Liefert die Dezimalgenauigkeit der Spalte, zurückgegeben als Anzahl der Ziffern.
  • intgetScale(intcolumn)
    Liefert die Genauigkeit der Spalte. Dies ist die Anzahl der Stellen, die nach dem Dezimalpunkt verwendet werden können.
  • StringgetSchemaName(intcolumn)
    Der Name des Tabellenschemas. Wird von den Methoden des DatabaseMetaData-Objekts benutzt. Falls kein Schema vorhanden ist, wird „“ zurückgegeben.
  • StringgetTableName(intcolumn)
    Liefert den Tabellennamen der angegebenen Spalte.
  • booleanisAutoIncrement(intcolumn)
    Stellt fest, ob eine Spalte eine Auto-Increment-Spalte ist. Diese nimmt dann automatisch den nächsten freien Wert an, wenn ein neuer Datensatz eingefügt wird. Ist die erste Zeile einer Tabelle mit einer Auto-Increment-Spalte eingefügt, so nimmt die Spalte den Wert 1 an. In den meisten Datenbanken ist es allerdings nicht möglich, eigene Werte in diesen Spalten einzutragen.
  • booleanisCaseSensitive(intcolumn)
    Berücksichtigt die Spalte die Groß- bzw. Kleinschreibung?
  • booleanisCurrency(intcolumn)
    Enthält die Spalte Geldwerte? Nur einige Datenbanken bieten diesen Spaltentyp.
  • booleanisNullable(intcolumn)
    Ist ein SQL-NULL in der Spalte erlaubt?
  • booleanisSearchable(intcolumn)
    Kann die Spalte in einer SQL-WHERE-Klausel verwendet werden?
  • booleanisSigned(intcolumn)
    Enthält die Spalte vorzeichenbehaftete Datentypen? Vorzeichenbehaftete Typen sind unter anderem INT, LONGINT und SMALLINT. Vorzeichenlose Typen sind unter anderem UINT, ULONG und UBYTE.
  • booleanisReadOnly(intcolumn)
    Ist es möglich, auf die Spalte definitiv nicht schreibend zuzugreifen? Ist das Ergebnis true, kann der Wert also nicht aktualisiert werden.
  • booleanisWritable(intcolumn)
    Ist es prinzipiell möglich, auf die Spalte schreibend zuzugreifen? Häufig wird das als !isReadOnly(column) implementiert.
  • booleanisDefinitelyWritable(intcolumn)
    Kann auf die Spalte definitiv schreibend zugegriffen werden? Viele Datenbanken liefern die gleichen Ergebnisse bei isDefinitelyWritable(…) und isWritable(…). Prinzipiell könnte der Zustand von isWritable(…) abweichen, wenn sich zum Beispiel die Schreibbarkeit dynamisch ändert.

Alle Methoden können eine SQLException auslösen.

Informationen über die Datenbank

Metadaten sind auch für die gesamte Datenbank abfragbar. Beispiele für diese Informationen sind:

  • Welche Tabellen liegen in der Datenbank?
  • Wer ist mit der Datenbank verbunden?
  • Kann die Datenbank nur gelesen oder kann auch in die Datenbank geschrieben werden?
  • Wie lauten die Primärschlüssel für eine Tabelle?
  • Sind gespeicherte Prozeduren auf der Datenbankseite erlaubt?
  • Lassen sich äußere Joins (outer joins) durchführen?

Sind Informationen über die Datenbank gefragt, so lassen sich über Metadaten eines DatabaseMetaData-Objekts beispielsweise Datenbankeigenschaften des Herstellers herausfinden. Zunächst benötigen wir dazu ein DatabaseMetaData-Objekt, das uns getMetaData() von einer Connection gibt. Das DatabaseMetaData-Objekt deklariert eine große Anzahl Methoden:

DatabaseMetaData meta = con.getMetaData();
System.out.println( "Product name " + meta.getDatabaseProductName() );
System.out.println( "Version: " + meta.getDatabaseProductVersion()  );
System.out.println( "Maximum number of connections: " + meta.getMaxConnections() );
System.out.println( "JDBC driver version: " + meta.getDriverVersion() );
System.out.println( "Supports update in batch: " + meta.supportsBatchUpdates() );
System.out.println( "Supports stored procedures: " + meta.supportsStoredProcedures() );

Inselraus: Groovy

Groovy (http://groovy.codehaus.org/) ist eine objektorientierte Skriptsprache, die auf einer JVM läuft und vollen Zugriff auf alle Java-Bibliotheken bietet. Die Sprache hat viele interessante Eigenschaften, die sie zu Java X machen. Groovy setzt auf der Standard-Java-Syntax auf und erweitert diese.[1] Einige Höhepunkte:

Groovy-Feature Beispiel
Statt System.out.print(…)/println(…) reicht ein print bzw. println, und dann muss das Argument auch nicht in runden Klammern stehen. println 1 + 2
print „Wow“
Anweisungen müssen nicht immer mit einem Semikolon abgeschlossen werden, sondern nur dann, wenn mehrere Anweisungen in einer Zeile stehen. print „Wow“
print „Wow“; print „Wow“
Variablen müssen nicht mit einem Typ deklariert werden, das gilt auch bei catch oder Methoden/Konstruktoren. def age = 40
def greet( name ) { print „Hallo “ + name }
try { } catch ( e ) { }
Zeichenketten lassen sich mit einfachen oder doppelten Anführungszeichen angeben. Einzelne Zeichen (char/Character) müssen besonders aufgebaut werden. print „Ja“ == ‚Ja‘      // true
def sign = „-“ as char
Strings in doppelten Anführungszeichen sind besondere GStrings: Enthalten sie $variable bzw. ${ausdruck}, so wird der Inhalt der Variablen bzw. der ausgewertete Ausdruck eingesetzt. def one = 1
print „$one plus $one macht ${1+1}“
// 1 plus 1 macht 2
Strings können über mehrere Zeilen laufen, wenn sie in „““ oder “‘ eingeschlossen sind – folgt ein Backslash am Ende der Zeile, wird kein Zeilenumbruchzeichen eingefügt. print „““
Zeile 1
Zeile 2\
Weiter mit Zeile 2
„““
Alles ungleich 0 und null ist true. if ( 1 ) print ‚Zweig wird genommen‘
Groovy definiert einen neuen Datentyp für Bereiche (engl. ranges). def numbers1 = 0..9
def numbers2 = 0..<10
Erweiterte Zählschleife unter Berücksichtigung von Bereichen, bzw. rechts von in steht ein Iterable wie beim erweiterten for. for ( i in 0..<10 )
print i  // 0123456789
== bei Referenzen bedeutet equals(…)-gleich; ein Identitätsvergleich realisiert is(…). def heinz = ‚Heinz‘
print heinz == ‚Heinz‘  // true
Operatoren können überladen werden, und die Anwendung von Operatoren entspricht Methodenaufrufen, etwa + von plus(…), ++ von next(…) oder [] von getAt(…). println 1 + 2 * 3 // 7
println 1.plus(2).multiply(3) // 9
println 1.plus(2.multiply(3)) // 7
Alle Comparable-Objekte (und damit etwa String, BigInteger, BigDecimal) können mit ==, !=, <, >, <= und >= verglichen werden. Die Ordnung liefert compareTo(…). def heinz = ‚Heinz‘
print heinz < ‚Werner‘  // true
Ist in Java nur java.lang standardmäßig importiert, ist es bei Groovy viel mehr, etwa noch java.io, java.util, java.net und noch einige. print new File(‚file.txt‘).exists()
Statt explizit auf Datenstrukturklassen für Listen und Mengen zurückzugreifen, bietet Groovy eine spezielle Syntax. def primes = [ 2, 3, 5, 7]
print primes[ 0 ]
def dic = [ ‚rot‘ : ‚red‘, ‚blau‘ : ‚blue‘ ]
print dic[‚rot‘]  // red
Die Operatoren ?. und ?: (Elvis-Operator) verhindern null-Zugriffe. def name = ‚Chris‘
println name ?: ‚Kein Name‘  // Chris
name = null
println name ?: ‚Kein Name‘  // Kein Name
Der Zugriff auf Zeichen über [] ist bei den Zeichenkettenklassen String, StringBuffer, StringBuilder möglich. def s = ‚Ooog‘
print s[0]   // O
Der Zugriff über [] lässt auch Bereiche zu. def s = „tutego“
print s[ 0, 1, 3..5 ] // tugeo
def t = new StringBuilder( „tutego“ )
t[ 1..4 ] = „X“
print t  // tXo
Der Operator << hängt Dinge an Strings (Rückgabe ist dann StringBuffer), Listen oder Dateien an. def s = ‚oger‘
def sb = ‚Das ist ‚ << s << ‚ oder?‘
print sb       // Das ist oger oder?
def file = new File(„out.txt“)
file << „Zeile 1“
Reguläre Ausdrücke werden mit einer eigenen Syntax direkt von der Sprache unterstützt; =~ ist ein find, ==~ ein match. print ‚2010‘ ==~ /\d+/
erweitertes switch-case etwa mit regulären Ausdrücken und Bereichen switch ( 42 ) {
case 0..100  : println ‚ist zwischen 0..100‘; break
case Integer : println ‚ist Integer‘; break
case { it % 2 == 0 }: println ‚ist gerade‘; break
case ~/\d\d/: println ’42 passt auf das Pattern‘; break
}
Datentyp für Dauer und spezielle Eigenschaften für Datumswerte und überladene Operatoren zum leichten Rechnen mit dem Paket groovy.time use ( groovy.time.TimeCategory ) {
def today = new Date()
def tomorrow = today + 1.day
print „Morgen: $tomorrow, nächstes Jahr ${today + 1.year}“
}
Closures als Codeblöcke werden unterstützt und finden überall in der Groovy-API Anwendung. new File( „file.txt“ ).eachLine {
println it
}
def names = [ „Charisma“, “  „, „Tina“, „“ ]
print names.findAll { ! it.trim().isEmpty() }
// [Charisma, Tina]
Setter/Getter müssen nicht aufgerufen werden; beim Zugriff ref.property wird automatisch der Setter/Getter aufgerufen. def p = new Point( 10, 20 )
print p.location.x  // 10.0
In Methoden mit Rückgabe kann das Schlüsselwort return entfallen. def add( a, b ) { a + b }
Default-Parameter bei Methodendeklarationen static tax( cost, taxRate = 19 ) {
cost * taxRate / 100
}
println tax( 100 )       // 19
println tax( 100, 7 )    // 7
Methoden und Klassen sind standardmäßig public, nicht paketsichtbar wie bei Java. Groovy erstellt automatisch Setter/Getter. class Person { def  name }
Mit der Klassenannotation @Immutable wird ein Typ unveränderbar, mit @Singleton ein Singleton mit einem privaten Konstruktor und öffentlichem Attribut instance für die eine Instanz. @Immutable class Pair { String val1, val2 }
@Singleton class AppWindow { }
Diverse Builder-Klassen erleichtern den Aufbau von hierarchischen XML- oder Swing-Bäumen ebenso wie Ant-Build-Skripten. import groovy.xml.MarkupBuilder
def writer = new StringWriter()
new MarkupBuilder( writer ).html {
head { title ‚Zählen mit Graf Zahl‘ }
body {
h1 ( ‚Willkommen‘ )
p {
ul { (1..10).each { li it } }
a( href:“’http://stupidedia.org/stupi/Graf_Zahl“‘,
‚Graf Zahl‘ )
}
}
}
println writer

Eigenschaften von Groovy mit Beispiel

Groovy-Skripte in Eclipse ausführen

Um Groovy-Skripte und Programme auszuführen, bieten sich drei Wege an:

  • über eine spezielle Kommandozeile, die Groovy-Shell
  • über das Eclipse-Plugin
  • über die javax.script-API

Das Eclipse-Plugin unter http://groovy.codehaus.org/Eclipse+Plugin leistet gute Arbeit und wird ständig weiterentwickelt. Auf der Webseite ist der Update-Link genannt, sodass Groovy-Eclipse über den Update-Manager installiert werden kann.

  1. Wähle dazu Eclipse Help • Install New Software…
  2. In der Dialogbox fülle das Textfeld bei Work with mit dem Link http://dist.springsource.org/release/GRECLIPSE/e4.3/ (bzw. anderen Versionen je nach Eclipse-Version), und aktiviere Add…
  3. Aktiviere anschließend Groovy-Eclipse, und führe die Installation mit Next usw. zu Ende.

Ist Eclipse neu gestartet und das Plugin installiert, kann jedes Java-Projekt um Groovy-Unterstützung erweitert werden. Bei einem Java-Projekt aktiviert die Aktion Configure • Convert to Groovy Project im Kontextmenü genau diese Groovy-Unterstützung, sodass im Projektbaum die Groovy-Bibliotheken auftauchen.

File • New • Other… ermöglicht zum einen im Zweig Groovy das Erstellen eines neuen Groovy-Projekts (durch die Konvertierung eines existierenden Java-Projekts ist das nicht mehr nötig) und zum anderen mit Groovy Class das Erstellen einer Groovy-Klasse. Bei dieser Option lässt sich ein Paket- und Klassenname eingeben (etwa com.tutego.insel.script und GroovyBabe), und dann öffnet Eclipse den Groovy-Editor.

Löschen wir die Klassendeklaration und setzen nur die zwei Zeilen in die Datei:

package com.tutego.insel.script

print „Hallo Groovy“

Ausgeführt wird das Groovy-Skript im Kontextmenü (oder Run-Menü) mit Run As • Groovy Skript.

Groovy über die Skript-API ausführen

Um Groovy als Skriptsprache aus einem Java-Programm heraus zu nutzen, können wir wieder auf die Skript-API zurückgreifen. Wenn es sich bei Eclipse schon um ein Groovy-Projekt handelt, ist ein JAR schon im Klassenpfad eingebunden, und es ist nichts zu tun. Andernfalls bekommen wir von der Webseite http://groovy.codehaus.org/Download unter Download zip: Binary Release ein fast 30 MiB großes ZIP-Archiv wie groovy-binary-2.2.2.zip, wo groovy-all-2.2.2.jar im Ordner embeddable liegt und das wir in den Klassenpfad mit aufnehmen.

Ein kleines Beispiel:

ScriptEngine engine = new ScriptEngineManager().getEngineByName( "groovy" );
 System.out.println( engine.eval( "(1g..42g.gcd(56g)).sum()" ) ); // 105
[1]    Bis auf sehr kleine Ausnahmen ist jedes Java-Programm ein Groovy-Programm.

Inselraus: Zeitdauern und der XML-Datentyp Duration

Eine der Schwachstellen in der Datumsverarbeitung ist das Fehlen eines Typs für Dauern (wenn wir von TimeUnit einmal absehen). Ein eigenständiger Typ bringt Vorteile, wenn es zum Beispiel darum geht, Dauern zu addieren (»Was ergibt 1 Woche plus 2 Monate?«) oder zu vergleichen (»Ist 1 Stunde mehr als 123456789 Millisekunden?«). Zwar lassen sich mit der add(…)-Methode von Calendar einzelne Segmente ändern, und dadurch lässt sich ein früherer oder späterer Zeitpunkt ansteuern, aber das ist wenig objektorientiert. Besser ist ein eigener Datentyp, der auch Operationen anbietet, um die Dauer auf ein Calendar- oder Date-Objekt zu setzen.

DatatypeFactory als Fabrik

Im Rahmen der W3C-XML-Schema-Unterstützung gibt es Klassen, unter anderem den Typ Duration für Dauern nach der gregorianischen Zeit. Die Klasse liegt jedoch nicht im java.util-Paket, sondern wegen ihres XML-Bezugs im Paket javax.xml.datatype (wo es neben XMLGregorianCalendar nahezu alleine liegt). Exemplare von Duration werden auch von keinem Konstruktor angelegt, sondern von einer Fabrikklasse DatatypeFactory. Beispiel: Lege ein Duration-Objekt mit der Dauer von 1 Tag und 2 Stunden an:

Duration d = DatatypeFactory.newInstance().newDuration(true, 0, 0, 1, 2, 0, 0 );
 System.out.println( d );  // P0Y0M1DT1H0M0S

Die DatatypeFactory bietet diverse Fabrikmethoden zum Anlegen der Duration-Objekte. Die Parameterlisten sind wie im Beispiel mitunter recht lang – in der längsten Variante gibt es einen Indikator für ein Vorzeichen, Jahr, Monat, Tag, Stunden, Minuten, Sekunden (keine Millisekunden oder genauer), also mit sieben Parametern. Ein Builder-Pattern zum Aufbau der Objekte wäre nett gewesen … abstract class javax.xml.datatype.DatatypeFactory

  • staticDatatypeFactorynewInstance()throwsDatatypeConfigurationException
  • DurationnewDuration(booleanisPositive,intyears,intmonths,intdays,inthours, intminutes,intseconds)
  • abstractDurationnewDuration(booleanisPositive,BigIntegeryears,BigIntegermonths, BigIntegerdays,BigIntegerhours,BigIntegerminutes,BigDecimalseconds)
  • abstractDurationnewDuration(longdurationInMilliSeconds)
  • abstractDurationnewDuration(StringlexicalRepresentation)
  • DurationnewDurationDayTime(booleanisPositive,BigIntegerday,BigIntegerhour,BigInteger   minute,BigIntegersecond)
  • DurationnewDurationDayTime(booleanisPositive,intday,inthour,intminute,intsecond)
  • DurationnewDurationDayTime(longdurationInMilliseconds)
  • DurationnewDurationDayTime(StringlexicalRepresentation)
  • DurationnewDurationYearMonth(booleanisPositive,BigIntegeryear,BigIntegermonth)
  • DurationnewDurationYearMonth(booleanisPositive,intyear,intmonth)
  • DurationnewDurationYearMonth(longdurationInMilliseconds)
  • DurationnewDurationYearMonth(StringlexicalRepresentation)

Die Duration-Klasse und ihre Methoden

Die Duration-Ausgabe im Beispiel über toString() liefert eine besondere String-Repräsentation, die für XML-Dokumente interessant ist, aber andere Methoden sind interessanter. Eine grobe Einteilung ergibt:

  • Anfragemethoden für die Segmente wie getYear(), getDay(), …
  • Vergleichsmethoden wie compare(Duration) oder isLongerThan(Duration)
  • Duration-Objekte sind immutable, doch gibt es Methoden wie add(Duration) oder multiply(Duration), die neue Duration-Objekte mit veränderten Segmenten zurückgeben, oder die Methode normalizeWith(Calendar), die Calendar-Felder zur Initialisierung nutzt.
  • Anwenden der Duration-Objekte auf Calendar oder Date mit addTo(Calendar)/ addTo(Date).

Beispiel: Addiere die Dauer von 2 Monaten und 3 Tagen, und berechne, wo wir dann relativ zu heute stehen:

DatatypeFactory datatypeFactory = DatatypeFactory.newInstance();
 Duration d1 = datatypeFactory.newDurationYearMonth( true, 0, 2 );
 Duration d2 = datatypeFactory.newDuration( true, 0, 0, 3, 0, 0, 0 );
 Duration sum = d1.add( d2 );
 Date date = new Date();
 System.out.printf( "%tF%n", date ); // 2011-06-21
 sum.addTo( date );
 System.out.printf( "%tF%n", date ); // 2011-08-24

Mögliche und unmögliche Operationen

Intern speichert die Duration-Implementierung jedes einzelne Segment und legt es nicht zu einer Zahl, etwa Sekunden zusammen. Das wäre auch nicht möglich, da die Anzahl Tage im Monat und im Jahr nicht immer gleich sind (dass zeigen uns der Februar und Schaltjahre). Wenn wir auf dem 1.1. einen Monat addieren, wollen wir beim 1.2. auskommen, und wenn wir bei 1.2. beginnen und einen Monat addieren, soll der 1.3. das Ergebnis sein. Beispiel: Additionen eines Monats auf einen Kalender:

DatatypeFactory datatypeFactory = DatatypeFactory.newInstance();
 Duration month = datatypeFactory.newDurationYearMonth( true, 0, 1 );
 Calendar cal = new GregorianCalendar( 2012, Calendar.JANUARY, 1 );
 month.addTo( cal );
 System.out.printf( "%tF%n", cal); // 2012-02-01
 month.addTo( cal );
 System.out.printf( "%tF%n", cal); // 2012-03-01

Da die Anzahl der Tage im Monat und im Jahr beweglich ist, sind bei add(…) und substract(…) nur gewisse Kombinationen möglich. Es gibt Operationen, die Duration nicht ausführen kann und mit einer IllegalStateException bestraft. Während innerhalb der Gruppe Sekunden, Minuten, Stunden und Tage beliebig addiert und subtrahiert werden können, ist der Übergang nach Monat und Jahr problematisch, insbesondere bei Subtraktionen. Beispiel: 1 Monat minus 1 Tag ist genauso wenig möglich wie 1 Jahr minus 1 Tag:

DatatypeFactory factory = DatatypeFactory.newInstance();
 Duration year  = factory.newDuration( true, 1, 0, 0, 0, 0, 0 );
 Duration month = factory.newDuration( true, 0, 1, 0, 0, 0, 0 );
 Duration day   = factory.newDuration( true, 0, 0, 1, 0, 0, 0 );
 year.subtract( day );  // N  IllegalStateException
 month.subtract( day ); // N  IllegalStateException

Duration-Vergleiche

Beim Vergleichen zweier Dauern gibt es vier unterschiedliche Ergebnisse, und daher implementiert Duration auch nicht die bekannte Comparable-Schnittstelle. Der Grund ist, dass einige Vergleiche nicht endscheidbar sind. So sind 30 Tage sind nicht automatisch 1 Monat, 365 Tage nicht automatisch 1 Jahr. Die compare(…)-Methode liefert Ganzahlen, die den jeweiligen Ausgang dokumentieren:

Vergleichsergebnis Beispiel Konstante
Dauer1 ist kürzer als Dauer2. 1 Minute ist kürzer als 100 Sekunden DatatypeConstants.LESSER
Dauer1 ist länger als Dauer2. 1 Tag ist länger als 1 Minute DatatypeConstants.GREATER
Dauer1 ist gleichlang Dauer2. 1 Minute ist gleich 60 Sekunden DatatypeConstants.EQUAL
Dauer1 ist unvergleichbar mit Dauer2. 30 Tage sind nicht automatisch 1 Monat DatatypeConstants.INDETERMINATE

Ausgang von Vergleichen zwischen Duration-Objekten Beispiel: Vergleiche 30 Tage mit 1 Monat:

DatatypeFactory factory = DatatypeFactory.newInstance();
 Duration month       = factory.newDurationYearMonth( true, 0, 1 );
 Duration thirtyDays  = factory.newDuration( true, 0, 0, 30, 0, 0, 0 );
 System.out.println( month.compare( thirtyDays ) ==
                     DatatypeConstants.INDETERMINATE );   // true
 System.out.println( month.isLongerThan( thirtyDays ) );  // false
 System.out.println( thirtyDays.isLongerThan( month ) );  // false

Wir sprechen bei Duration daher auch nur von einer partiellen Ordnung statt von einer vollständigen Ordnung.

Zusammenfassung der Duration-Methoden

abstract class javax.xml.datatype.DatatypeFactory

  • abstractintgetSign()
  • intgetYears()
  • intgetMonths()
  • intgetDays()
  • intgetHours()
  • intgetMinutes()
  • intgetSeconds()
  • abstractDurationadd(Durationrhs)
  • Durationsubtract(Durationrhs)
  • Durationmultiply(intfactor)
  • abstractDurationmultiply(BigDecimalfactor)
  • abstractDurationnegate()
  • abstractintcompare(Durationduration)
  • booleanisLongerThan(Durationduration)
  • booleanisShorterThan(Durationduration)
  • abstractvoidaddTo(Calendarcalendar)
  • voidaddTo(Datedate)
  • abstractDurationnormalizeWith(CalendarstartTimeInstant)
  • longgetTimeInMillis(CalendarstartInstant)
  • longgetTimeInMillis(DatestartInstant)
  • abstractbooleanisSet(DatatypeConstants.Fieldfield)
  • abstractNumbergetField(DatatypeConstants.Fieldfield)
  • QNamegetXMLSchemaType()
  • abstractinthashCode()
  • booleanequals(Objectduration)
  • StringtoString()

Seit dem in Java 8 die Java Date & Time API eingezogen ist, muss man nicht mehr zu diesen Typen greifen, es sei denn, mann arbeitet direkt mit der XML-API.

Funktionale Schnittstelle in Java 8 aus java.util.function

Funktionen realisieren Abbildungen und da es verschiedene Arten von Abbildungen geben kann, bietet die Java Standardbibliothek im Paket java.util.function für die häufigsten Fälle funktionale Schnittstellen an. Ein erster Überblick:

Schnittstelle Abbildung
Consumer<T> (T) → void
DoubleConsumer (double) → void
BiConsumer<T, U> (T, U) → void
Supplier<T> () → T
BooleanSupplier () → boolean
Predicate<T> (T) → boolean
LongPredicate (long) → boolean
BiPredicate<T, U> (T, U) → boolean
Function<T, R> (T) → R
LongToDoubleFunction (long) → double
BiFunction<T, U, R> (T, U) → R
UnaryOperator<T> (T) → T
DoubleBinaryOperator (double) → boolean

Beispiele einiger vordefinierter funktionaler Schnittstellen

Weiterlesen