File-Klassen auf NIO.2 umstellen

Um sich von der Klasse java.io.File zu lösen und in Richtung Path zu migrieren, ist Handarbeit angesagt. Zunächst gilt es, alle Stellen zu finden, die im Workspace oder Projekt die Klasse File referenzieren. Am Einfachsten ist es, eine Anweisung wie File f; in den Code zu setzen, dann zum Beispiel in Eclipse Strg+Shift+G zu aktivieren. Es folgt eine Liste aller Vorkommen. Diese Liste muss nun abgearbeitet werden und der Code auf NIO.2 konvertiert werden. Nicht immer ist es so einfach

Scanner s = new Scanner( new File(dateiname) );

in

Scanner s = new Scanner( Paths.get(dateiname) );

umzusetzen oder

new File( dateiname ).delete();

in

Files.delete( Paths.get( dateiname ) );

Eine andere Stelle, an der Konvertierungen möglich sind, betreffen FileReader und FileWriter. Diese Klassen sind gefährlich, weil sie standardmäßig die im System voreingestellte Kodierung verwenden. Eine Kodierung wie UTF-8 im Konstruktor explizit vorzugeben ist jedoch nicht möglich. NIO.2 bietet eine bessere Methode, um an einen Reader/Writer aus einer Datei zu kommen und Entwickler werden gezwungen, die Kodierung immer sichtbar anzugeben.

Deklaration

Klassisch

Mit NIO.2

Reader r =

new FileReader(f);

Files.newBufferedReader(Paths.get(f),

StandardCharsets.ISO_8859_1);

Writer w =

new FileWriter(f);

Files.newBufferedWriter(Paths.get(f),

StandardCharsets.ISO_8859_1);

Beziehen eines Dateistroms klassisch und mit NIO.2

Der Quellcode wird erst einmal länger, doch der Gewinn ist, dass die Kodierung nicht verschwindet und auch die Ein-/Ausgabe gleich gepuffert ist. Freunde der statischen Import-Anweisung komprimieren weiterhin zu Anweisungen wie

Reader r = newBufferedReader( get( f ), ISO_8859_1 );

Es ist auf den Fall Handarbeit angesagt. Eigene Blöcke, etwa zum Schreiben in Dateien können komplett zusammengestrichen werden auf einfache Methodenaufrufe wie

Files.write( Paths.get( f ), bytes );

List<String> lines = Files.readAllLines( Paths.get( f ), StandardCharsets.ISO_8859_1 );

Hilfe bei der Migration auf Java 7 mit NetBeans und Eclipse

Wer eine große Codebasis auf Java 7 migrieren möchte, steht vor dem Programm, wie das ohne großen manuellen Aufwand funktionieren kann. Als erstes steht die Umsetzung auf die neuen Syntax-Möglichkeiten an. Das ist ein echter Mehrwert, bei den Bibliotheken gibt es in dem Sinne keine Umsetzung von alt nach neu, die File-Klasse ist nicht als veraltet markiert, wobei es durchaus interessant ist, auch File-Operationen durch die NIO.2-Operationen zu ersetzen. Schauen wir uns an, welche Möglichkeiten die modernen IDEs bieten.

Migration mit NetBeans

Die aktuelle NetBeans IDE bringt einen Code-Transformer mit, der elegant die Code-Basis durchsucht und Transformationen automatisch durchführt. Das Menü Refactor bietet den Unterpunkt Inspect and Transform…, der zu einem Dialog führt.

Hier lässt sich bei Configuration auswählen, ob imports organisiert, oder in Java 7 transformiert werden soll. Unter Manage… öffnet sich ein weiter Dialog, der im Baum JDK 1.5 and later die erkannten Muster anzeigt. Für Java 7 ist dies Can use Diamond, Convert to try-with-resources, Join catch sections using mulitcatch, Use specific catch und Use switch over Strings when possible.

Wer neu Programmcode mit NetBeans schreibt wird gleich auf die neue Syntax hingewiesen. Vervollständigungen nutzen ganz natürlich zum Beispiel den Diamond-Operator.

Migration mit Eclipse

Eclipse integriert kein Werkzeug, um die Code-Basis automatisch in die Syntax von Java 7 zu konvertieren. Seit der Version Eclipse 3.7 sind jedoch Transformationen über die Quick-Fix/Quick-Assist möglich, was es erlaubt, Generics zu mit dem Diamanten zu verkürzen, Ausnahme-Anweisungen mit einem Multi-Catch zu umrahmen, gleiche Catch-Blöcke mit einem Multi-Catch zusammenzufassen und AutoCloseable-Resourcen mit einem try-mit-Resourcen zu schließen. Allerdings setzt Eclipse bis auf eine mögliche Diamanten-Umsetzung keine Warnung ein. Und diese Warnung ist standardmäßig abgeschaltet und muss aktiviert werden. Dazu wählte Window > Preferences im Baum Java > Compiler > Errors/Warning rechts in der Rubrik Generic Type bei Redundant type arguments statt Ignore dann Warning.

Inselraus: Swing/Auswahlmenü mit Separator

Standardmäßig unterstützt die JList und auch JComboBox keine Separatoren, doch die Unterteilung in Segmente ist in der Praxis sehr nützlich. Microsoft Word und PowerPoint benutzen sie zum Beispiel, um die zuletzt vom Benutzer ausgewählten Zeichensätze prominent oben in der Liste zu haben (Excel dagegen tut dies nicht).

Abbildung

Abbildung 9.42: Separator in Words Zeichensatzauswahlliste

Wir wollen diese Möglichkeit nachbilden und dabei noch einiges über Modelle und Renderer lernen.

Abbildung

Abbildung 9.43: Eine eigene Variante mit JSeparator in JComboBox

Bei der Umsetzung gibt es unterschiedliche Varianten, die sich außer in der technischen Implementierung darin unterscheiden, ob das Modell eine Markierung für den Separator enthält oder nicht. Wir stellen beide Ansätze vor und beginnen mit der ersten Variante, also damit, einem Zellen-Renderer Positionen mitzugeben, die sagen, wo ein Trennstrich zu zeichnen ist.

package com.tutego.insel.ui.list;

import javax.swing.*;

public class JComboBoxWithSeparator1 {
  public static void main( String[] args ) throws Exception {
    UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() );
    JFrame frame = new JFrame();
    frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
    String[] items = { "Cambria", "Arial", "Verdana", "Times" };
    JComboBox<String> comboBox = new JComboBox<>( items );
    ListCellRenderer<String> renderer = new SeparatorAwareListCellRenderer1<>(
                                          comboBox.getRenderer(), 0 );
    comboBox.setRenderer( renderer );
    frame.add( comboBox );
    frame.pack();
    frame.setVisible( true );
  }
}

Die eigene Klasse SeparatorAwareListCellRenderer1 ist ein ListCellRenderer, den die JComboBox zur Darstellung der Komponenten nutzt. Im Konstruktor des Renderers geben wir den Original-Renderer mit – es kann ein bestimmter Renderer schon vorinstalliert sein, den wollen wir dekorieren – sowie eine variable Argumentliste von Positionen. Das Beispiel übergibt nur 0, da nach dem ersten Element (Index = 0) ein Trennzeichen zu setzen sein soll, sodass Cambria und Arial abgetrennt sind.

package com.tutego.insel.ui.list;

import java.awt.*;
import java.util.Arrays;

import javax.swing.*;

public class SeparatorAwareListCellRenderer1<E> implements ListCellRenderer<E>
{
  private final ListCellRenderer<? super E> delegate;
  private final int[] indexes;
  private final JPanel panel = new JPanel( new BorderLayout() );

  public SeparatorAwareListCellRenderer1( ListCellRenderer<? super E> delegate,
                                          int... indexes )
  {
    Arrays.sort( indexes );
    this.delegate = delegate;
    this.indexes  = indexes;
  }

  @Override
  public Component getListCellRendererComponent( JList<? extends E> list, E value,
                                                 int index, boolean isSelected,
                                                 boolean cellHasFocus )
  {
    panel.removeAll();
    panel.add( delegate.getListCellRendererComponent( list, value, index,
                                                      isSelected, cellHasFocus ) );
    if ( Arrays.binarySearch( indexes, index ) >= 0 )
      panel.add( new JSeparator(), BorderLayout.PAGE_END );

    return panel;
  }
}

Die Implementierung basiert auf der Idee, jede Komponente in einen Container (JPanel) zu setzen und in diesem Container dann je nach Bedarf ein JSeparatorunten an diesem Container anzufügen. Statt JPanel mit einem JSeparator auszustatten, kann ebenfalls auch ein Border unten gezeichnet werden. Die AnweisungArrays.binarySearch(indexes, index) >= 0 ist als »contains« zu verstehen, also als ein Test, ob der Index im Feld ist – leider gibt es so eine Methode nicht in der Java API. Wenn der Index im Feld ist, soll der Separator unter der Komponente erscheinen. Dass sich eine Trennlinie auch am Anfang befinden kann, berücksichtigt die Lösung nicht; diese Variante bleibt als Übungsaufgabe für die Leser.

Diese Lösung ist einfach und funktioniert gut, denn vorhandene Renderer werden weiterverwendet, was sehr wichtig ist, denn größere Swing-Anwendungen nutzen viele eigene Renderer, etwa um Icons und Text zusammenzufassen. Ein Nachteil ist, dass der Separator zu einen Element gehört, und wenn das Element etwa in der Liste ausgewählt wird, steht der Separator mit in der Selektion und ist nicht abgetrennt (das ist aber bei Word genauso).

Während die vorgestellte Variante in der Praxis gut funktioniert, wollen wir uns noch mit einer alternativen Umsetzung beschäftigen. Sie ist deutlich komplexer und auch nicht so flexibel. Die Lösung basiert auf der Idee, dass die Modelldaten eine Markierung für den Separator enthalten – die folgende Lösung nutzt null dafür. DaJList oder JComboBox eine null problemlos verträgt, ist die Basis schnell umgesetzt:

JFrame frame = new JFrame();
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
String[] items = { "Cambria", null, "Arial", "Cambria", "Verdana", "Times" };
JComboBox<String> comboBox = new JComboBox<String>( items );
frame.add( comboBox );
frame.pack();
frame.setVisible( true );

Wenn wir das Programm starten, zeigt es dort, wo das Model eine null liefert, einfach nichts an. Die Selektion dieses null-Elements ist auch möglich und führt zu keiner Ausnahme.

Damit Swing das null-Element nicht als Leereintrag anzeigt, verpassen wir unserer JComboBox einen Zellen-Renderer. Der soll immer dann, wenn das Element nullist, ein Exemplar von JSeparator zeichnen. Vom Design ist es das beste, wenn der Zell-Renderer selbst die null-Elemente darstellt, aber das Zeichnen der Nicht-null-Elemente an einen anderen Zell-Renderer abgibt, anstatt diese etwa durch einen BasicComboBoxRenderer (ein JLabel, das ListCellRendererimplementiert) selbst zu renderen – das würde die Flexibilität der Lösung massiv einschränken.

package com.tutego.insel.ui.list;

import java.awt.Component;
import javax.swing.*;

public class SeparatorAwareListCellRenderer2<E> implements ListCellRenderer<E>
{
  private final ListCellRenderer<? super E> delegate;

  public SeparatorAwareListCellRenderer2( ListCellRenderer<? super E> delegate )
  {
    this.delegate = delegate;
  }

  @Override
  public Component getListCellRendererComponent( JList<? extends E> list, E value,
                                                 int index, boolean isSelected,
                                                 boolean cellHasFocus )
  {
    if ( value == null )
      return new JSeparator();

    return delegate.getListCellRendererComponent( list, value, index, isSelected, cellHasFocus );
  }
}

Die eigene Klasse SeparatorAwareListCellRenderer2 bekommt einen anderen ListCellRenderer übergeben und liefert die Komponenten dieses Delegate-Renderers immer dann, wenn das Element ungleich null ist. Ist es dagegen gleich null, liefert getListCellRendererComponent() ein Exemplar des JSeparators.

Im Beispielprogramm muss der Renderer nun angemeldet werden, und dazu ist Folgendes zu ergänzen:

comboBox.setRenderer(
  new SeparatorAwareListCellRenderer2<String>(comboBox.getRenderer()) );

Nach dem Start des Programms ist das Ergebnis schon viel besser: Dort, wo vorher die JComboBox eine leere Zeile darstellte, ist nun ein Strich.

Die Lösung ist jedoch nur ein Etappensieg, denn die Navigation mit der Tastatur durch die Liste zeigt eine Schwachstelle: Das null-Element lässt sich auswählen und erscheint auch als Linie im Editor/Textfeld. Beheben lässt sich das Problem nicht mit dem Renderer, denn der ist nur mit der Darstellung in der Liste beauftragt. Hier muss in die Interna der Swing-Komponente eingegriffen werden. Jede Swing-Komponente hat ein korrespondierendes UI-Delegate, das für das Verhalten und die Darstellung der Komponente verantwortlich ist. Für die JComboBox sind das Unterklassen von ComboBoxUI, und zwei Methoden sind besonders interessant:selectNextPossibleValue() und selectPreviousPossibleValue().

Da jede UI-Implementierung ihr eigenes Look and Feel (LAF) mitbringt, müssen wir hier eigentlich einen Dekorator bauen und jede Methode bis auf die beiden genannten an das Original weiterleiten, doch das ist jetzt zu viel Arbeit, und so nehmen wir die Basisklasse WindowsComboBoxUI als Basisklasse, denn unser Beispiel nutzt das Windows-LAF. In der Unterklasse implementieren wir eigene Versionen der Methoden selectNextPossibleValue() undselectPreviousPossibleValue(), die so lange die Liste nach oben/unten laufen müssen, bis sie ein Element ungleich null finden.

package com.tutego.insel.ui.list;

import com.sun.java.swing.plaf.windows.WindowsComboBoxUI;

public class SeparatorAwareComboBoxUI extends WindowsComboBoxUI
{
  @Override
  protected void selectNextPossibleValue()
  {
    for ( int index = comboBox.getSelectedIndex() + 1;
          index < comboBox.getItemCount();
          index++ )
      if ( comboBox.getItemAt( index ) != null )
      {
        comboBox.setSelectedIndex( index );
        break;
      }
  }

  @Override
  protected void selectPreviousPossibleValue()
  {
    for ( int index = comboBox.getSelectedIndex() - 1;
          index >= 0;
          index-- )
      if ( comboBox.getItemAt( index ) != null )
      {
        comboBox.setSelectedIndex( index );
        break;
      }
  }
}

Das UI-Objekt muss ebenfalls im main()-Programm angemeldet werden:

comboBox.setUI( new SeparatorAwareComboBoxUI() );

Enthält die Liste null-Elemente, überspringt die Tastennavigation über die Cursor-Taste diese. Doch auch mit diesem Teilstück fehlt ein weiteres Detail: Mit einem Klick lässt sich die Linie doch noch auswählen. Das ist keine Frage des Renderers und auch keine Frage der Tastaturnavigation – es muss untersagt werden, dass bei der Aktivierung ein null-Element zum Editor kommen kann. Hier ist die Methode setSelectedItem() von JComboBox entscheidend. Denn jedes Element, das selektiert wird – und dadurch auch in das Textfeld kommt –, geht durch die Methode hindurch. Wenn wir die Methode überschreiben und bei null-Elementen einfach nichts tun, wird auch das null-Element nicht selektiert, und im Textfeld bleibt das letzte Element stehen.

Damit auch spezielle Implementierungen von JComboBox von diesem Verhalten profitieren können, müssen wir wieder einen Dekorator schreiben, doch das kostet zu viel Mühe, und so überschreibt eine einfache Unterklasse die setSelectedItem()-Methode. (Im Prinzip wäre auch eine überschriebene Methode vonsetSelectedIndex() sinnvoll, denn das könnte eine programmierte Aktivierung von null vermeiden.)

package com.tutego.insel.ui.list;

import javax.swing.*;

public class SeparatorAwareJComboBox<E> extends JComboBox<E>
{
  @SafeVarargs
  public SeparatorAwareJComboBox( E... items )
  {
    super( items );
  }

  @Override
  public void setSelectedItem( Object anObject )
  {
    if ( anObject != null )
      super.setSelectedItem( anObject );
  }
}

Im Hauptprogramm muss nur diese spezielle Klasse zum Einsatz kommen und so folgt:

package com.tutego.insel.ui.list;

import javax.swing.*;

public class JComboBoxWithSeparator2
{
  public static void main( String[] args ) throws Exception
  {
    UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() );
//    UIManager.setLookAndFeel( new NimbusLookAndFeel() );

    JFrame frame = new JFrame();
    frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
    String[] items = { "Cambria", null, "Arial", "Verdana", "Times" };
    JComboBox<String> comboBox = new SeparatorAwareJComboBox<>( items );
    comboBox.setRenderer( new SeparatorAwareListCellRenderer2<>(comboBox.getRenderer()) );
    comboBox.setUI( new SeparatorAwareComboBoxUI() );
    comboBox.setSelectedIndex( 1 );
    frame.add( comboBox );
    frame.pack();
    frame.setVisible( true );
  }
}

Inselraus: Variablen mit Xor vertauschen

Eine besonders trickreiche Idee für das Vertauschen von Variableninhalten arbeitet mit dem Xor-Operator und benötigt keine temporäre Zwischenvariable. Die Zeilen zum Vertauschen von x und y lauten wie folgt:

int x = 12, 
  
    y = 49;
x ^= y; // x = x ^ y = 001100bin ^ 110001bin = 111101bin
y ^= x; // y = y ^ x = 110001bin ^ 111101bin = 001100bin
x ^= y; // x = x ^ y = 111101bin ^ 001100bin = 110001bin
System.out.println( x + " " + y );  // Ausgabe ist: 49 12

Der Trick funktioniert, da wir mit Xor etwas »hinein- und herausrechnen« können. Zuerst rechnet die erste Zeile das y in das x. Wenn wir anschließend die Zuweisung an das y machen, dann ist das der letzte schreibende Zugriff auf y, also muss hier schon das vertauschte Ergebnis stehen. Das stimmt auch, denn expandieren wir die zweite Zeile, steht dort: »y ^ x wird zugewiesen an y«, und dies ist y ^ (x ^ y). Der letzte Ausdruck verkürzt sich zu y = x, da aus der Definition des Xor-Operators für einen Wert a hervorgeht: a ^ a = 0. Die Zuweisung hätten wir zwar gleich so schreiben können, aber dann wäre der Wert von y verloren gegangen. Der steckt aber noch in x aus der ersten Zuweisung. Betrachten wir daher die letzte Zeile x ^ y: y hat den Startwert von x, doch in x steckt ein Xor-y. Daher ergibt x ^ y den Wert x ^ x ^ y, und der verkürzt sich zu y. Demnach haben wir den Inhalt der Variablen vertauscht. Im Übrigen können wir für die drei Xor-Zeilen alternativ schreiben:

y ^= x ^= y;   // Auswertung automatisch y ^= (x ^= y) 
  
x ^= y;

Da liegt es doch nahe, die Ausdrücke weiter abzukürzen zu x ^= y ^= x ^= y. Doch leider ist das falsch (es kommt für x immer null heraus). Motivierten Lesern bleibt dies als Denksportaufgabe überlassen.

Inselraus: identityHashCode() ist für eine System-eindeutige Objekt-IDs nicht geeignet

Stellen wir uns vor, wir hätten eine Objekthierarchie im Speicher, die zum Beispiel jeder Socke einen Besitzer zuspricht. Wenn wir im Speicher Assoziationen abbilden, dann sollen diese Verweise auch noch nach dem Tod des Programms überleben. Eine Lösung ist die Serialisierung, eine andere eine Objekt-Datenbank oder aber auch eine XML-Datei. Doch überlegen wir selbst, wo bei der Abbildung auf eine Datenbank oder eine Datei das Problem besteht. Zunächst stehen ganz unterschiedliche Objekte mit ihren Eigenschaften im Speicher. Das Speichern der Zustände ist kein Problem, denn nur die Attribute müssten abgespeichert werden. Doch wenn ein Objekt auf ein anderes verweist, muss dieser Verweis gesichert werden. Aber in Java ist ein Verweis durch eine Referenz gegeben, und was sollte es da zu speichern geben? Eine Lösung für das Problem ist, jedem Objekt im Speicher einen Zähler zu geben und beim Speichen etwa zu sagen: »Der Besitzer 2 kennt Socke 5«.

Der Identifizierer für die Objekte muss eindeutig sein, und wir können überlegen, System.identityHashCode() zu nutzen. In der Implementierung der virtuellen Maschine von Oracle geht in den Wert von identityHashCode() die Information über den wahren Ort des Objekts im Speicher ein. Bei einer 64-Bit-Implementierung würden auch 32 Bit abgeschnitten, und die Eindeutigkeit wäre somit automatisch nicht mehr gewährleistet. Ein weiteres Problem besteht darin, dass zwar die Implementierung von Oracles identityHashCode() auf die eindeutige Objektspeicheradresse abbildet, aber dass das nicht jeder Hersteller so machen muss. Damit ist identityHashCode() nicht überall gesichert unterschiedlich. Zudem ist es prinzipiell denkbar, dass die Speicherverwaltung die Objekte verschiebt. Was sollte identityHashCode() dann machen? Wenn die neue Speicheradresse dahinter steckt, würde sich der Hashcode ändern, und das darf nicht sein. Es käme ebenfalls zu einem Problem, wenn mehr als Integer.MAX_INTEGER viele Objekte im Speicher stünden. (Doch wenn wir uns die große Zahl 2^32 = 4.294.967.296 vor Augen halten, dann es ist unwahrscheinlich, dass sich mehr als 4 Milliarden Objekte im Speicher tummeln. Zudem bräuchten wir 4 Gigabyte Speicher, wenn jedes Objekt auch nur 1 Byte kosten würde.)

Es ist gar nicht so schwierig, zwei unterschiedliche Objekte mit gleichen identityHashCode()-Resultat zu bekommen. Wir erzeugen ein paar String-Objekte und testen, jedes mit jedem, ob identityHashCode() den gleichen Wert ergibt:

String[] strings = new String[5000];
for ( int i = 0; i < strings.length; i++ )
  strings[i] = Integer.toString( i );
int cnt = 0;
for ( int i = 0; i < strings.length; i++ ) {
  for ( int j = i + 1; j < strings.length; j++ ) {
    int id1 = System.identityHashCode( strings[i] );
    int id2 = System.identityHashCode( strings[j] );
    if ( id1 == id2 ) {
      out.println( "Zwei Objekte mit identityHashCode() = " + id1 );
      out.println( " Objekt 1: \"" + strings[i] + "\"" );
      out.println( " Objekt 2: \"" + strings[j] + "\"" );
      out.println( " Object1.hashCode(): " + strings[i].hashCode() );
      out.println( " Object2.hashCode(): " + strings[j].hashCode() );
      out.println( " Object1.equals(Object2): " + strings[i].equals( strings[j] ) );
      cnt++;
    }
  }
}
System.out.println( cnt + " Objekte mit gleichem identityHashCode() gefunden." );

Ein Durchlauf bringt schnell Ergebnisse wie:

Zwei Objekte mit identityHashCode() = 9578500
Objekt 1: "541"
Objekt 2: "2066"
Object1.hashCode(): 52594
Object2.hashCode(): 1537406
Object1.equals(Object2): false
Zwei Objekte mit identityHashCode() = 14850080
Objekt 1: "2085"
Objekt 2: "2365"
Object1.hashCode(): 1537467
Object2.hashCode(): 1540288
Object1.equals(Object2): false
2 Objekte mit gleichem identityHashCode() gefunden.

Das Ergebnis ist also, dass identityHashCode() nicht sicher bei der Vergabe von Identifizierern ist. Um wirklich allen Problemen aus dem Weg zu gehen, ist ein Zählerobjekt oder eine ID über zum Beispiel die Klasse java.util.UUID nötig.

Inselupdate: Gepoolte Datenbankverbindungen

Da der Auf- und Abbau von Datenbankverbindungen relativ teuer ist, soll eine Java-Applikation die Verbindung nur vordergründig schließen, ein spezieller pooling-fähiger Datenbanktreiber soll die Verbindung allerdings für die nächste Operation offen halten. Für gepoolte Datenbankverbindungen gibt es eine Reihe quelloffener Implementierungen; zu ihnen zählen Apache Commons DBCP (http://commons.apache.org/dbcp/), c3p0 (http://sourceforge.net/projects/c3p0/) und Proxool (http://proxool.sourceforge.net/).

In der Regel sind für den Programmierer gepoolete Datenbankverbindungen transparent, denn ein poolender Treiber verhält sich wie ein ganz normaler JDBC-Treiber. Jeder Java EE-Container nutzt standardmäßig gepoolte Verbindungen, sodass sich Enterprise-Entwickler damit nicht beschäftigen müssen – nur Administratoren müssen spezielle Eigenschaften wie die maximale Anzahl offener Verbindungen konfigurieren. In Client-Anwendungen ist die Nutzung anders, auch der Bezug einer Verbindung. Bei Proxool wird zum Beispiel statt der JDBC-Treiberklasse die Proxool-Treiberklasse genannt, und die der Verbindungs-URL kodiert dann die eigentlichen JDBC-Treiberklasse; das sieht etwa so aus: Class.forName("org.logicalcobwebs.proxool.ProxoolDriver"); con = DriverManager.getConnection("proxool.example:org.hsqldb.jdbcDriver:jdbc:hsqldb:file:TutegoDB");. Anders ist das bei c3po, hier wird eine spezielle Klasse ComboPooledDataSource eingesetzt, die vom Typ DataSource ist.[1]

Simple-JNDI (http://openbook.galileocomputing.de/javainsel9/javainsel_24_011.htm) unterstützt DBCP direkt, sodass wir es an dieser Stelle einsetzen wollen. Eine zusätzliche Zeile pool=true in der Konfigurationsdatei veranlasst Simple-JNDI dazu:

Listing 1.14: TutegoDS.properties

type=javax.sql.DataSource
driver=org.hsqldb.jdbcDriver
url=jdbc:hsqldb:file:TutegoDB;shutdown=true
user=sa
password=
pool=true

Dazu sind in den Klassenpfad das Haupt-Archiv commons-dbcp-1.4.jar (Download unter http://commons.apache.org/dbcp/download_dbcp.cgi) und zusätzlich das Java-Archiv commons-pool-1.3.jar (von Jakarta Commons Pool, Download unter http://commons.apache.org/pool/download_pool.cgi) aufzunehmen.


[1] Weitere Details unter http://www.mchange.com/projects/c3p0/index.html.

NetBeans 7.1 Code-Transformation nach Java 7

Wähle im Menü Refactor > Inspect and Transform … und dann im Dialog bei Use:/Configuration: den Eintrag Convert to Java 7 aus. Ein Klick auf Inspect startet die Suche und listet Änderungsmöglichkeiten auf und bietet an, die Stellen automatisch zu beheben.

Ich habe das Tool auf den Beispielen meines Buches angewendet und die meisten Hinweise beziehen sich auf Diamond und natürlich im IO-Kapitel auf auf try-mit-Ressourcen. Damit habe ich heute den ganzen Tag verbracht und auch noch ein paar kleine Fehler gefunden. Eine Stelle ist interessant:

XMLOutputFactory factory = XMLOutputFactory.newInstance();
XMLStreamWriter writer = factory.createXMLStreamWriter( new FileOutputStream( "writenParty.xml" ) );

XMLStreamWriter hat close() aber kein AutoCloseable. Kann man als Fehler ansehen.

Das hier formt der Konverter nicht um, er beginnt erst bei “fett” mit dem switch, das liegt am zweiten if, was nicht als if else formuliert ist.

if ( "Ende".equals(e.getActionCommand()) )
  System.exit( 0 );
if ( "fett".equals(e.getActionCommand()) )
  t.setFont( font = font.deriveFont( font.getStyle() ^ Font.BOLD ) );
else if ( "kursiv".equals(e.getActionCommand()) )
  t.setFont( font = font.deriveFont( font.getStyle() ^ Font.ITALIC ) );

Inselupdate: Vererbung und Überschattung von statischen Variablen

Die Konstanten einer Schnittstelle können einer anderen Schnittstelle vererbt werden. Dabei gibt es einige kleine Einschränkungen. Wir wollen an einem Beispiel sehen, wie sich die Vererbung auswirkt, wenn gleiche Bezeichner in den Unterschnittstellen erneut verwendet werden. Als Basis unseres Beispiels steht eine Schnittstelle BaseColors mit ein paar Deklarationen von Farben. Zwei Unterschnittstellen erweiterten BaseColor, einmal CarColors und PlaneColors, die für Farbdeklarationen für Autos und Flugzeuge stehen. Eine besondere Schnittstelle FlyingCarColors erweitert die beiden Schnittstelen CarColors und PlaneColors, denn es gibt auch fliegende Autos, die eine Farbe haben können.

interface BaseColors
{
  int WHITE   = 0;
  int BLACK   = 1;
  int GREY    = 2;
}

interface CarColors extends BaseColors
{
  int WHITE   = 1;
  int BLACK   = 0;
}

interface PlaneColors extends BaseColors
{
  int WHITE   = 0;
  int GREY    = 2;
}

interface FlyingCarColors extends CarColors, PlaneColors
{
}

public class Colors
{
  public static void main( String[] args )
  {
    System.out.println( BaseColors.GREY );      // 2
    System.out.println( CarColors.GREY );       // 2
    System.out.println( BaseColors.BLACK );     // 1
    System.out.println( CarColors.BLACK );      // 0
    System.out.println( PlaneColors.BLACK );    // 1

    System.out.println( FlyingCarColors.WHITE );//  The field FlyingCarColors.WHITE is ambiguous
    System.out.println( FlyingCarColors.GREY ); //  The field FlyingCarColors.GREY is ambiguous
  }
}

Die erste wichtige Tatsache ist, dass unsere drei Schnittstellen ohne Fehler übersetzt werden können, aber nicht die Klasse Colors. Das Programm und der Compiler zeigen folgendes Verhalten:

  • Schnittstellen vererben ihre Eigenschaften an die Unterschnittstellen. CarColors und auch PlaneColors erbten die Farbe WHITE, BLACK und GREY aus BaseColors.
  • Konstanten dürfen überschattet werden. CarColors vertauscht die Farbdeklarationen von WHITE und BLACK und gibt ihnen neue Werte. Wird jetzt der Wert CarColors.BLACK verlangt, liefert die Umgebung den Wert 0, während CarColors.BLACK 1 ergibt. Auch PlaneColor überdeckt die Konstanten WHITE und GREY obwohl sie Farbe mit dem gleichen Wert belegt sind.
  • Erbt eine Schnittstelle von mehreren Oberschnittstellen, so ist es zulässig, wenn die Oberschnittstellen jeweils ein gleichlautendes Attribut haben. So erbt etwa FlyingCarColors von CarColors und PlaneColors den Eintrag WHITE, BLACK und GREY.
  • Unterschnittstellen können aus zwei Oberschnittstellen die Attribute gleichen Namens übernehmen, auch wenn sie einen unterschiedlichen Wert haben. Das testet der Compiler nicht. FlyingCarColors bekommt aus CarColors ein WHITE mit 1 aber aus PlaneColors das Weiß mit 0. Daher ist auch der Zugriff FlyingCarColors.WHITE in dem Beispiel Colors auch nicht möglich und führt zu einem Compilerfehler. Bei der Benutzung muss ein unmissverständlicher qualifizierter Name verwendet werden, der deutlich macht, welches Attribut gemeint ist, also zum Beispiel CarColors.WHITE oder PlaneColors.WHITE. Ähnliches gilt für die Farbe GREY. Obwohl Grau durch die ursprüngliche Deklaration bei BaseColor und auch bei der Überschattung in PlaneColors immer Zwei ist, ist die Nutzung durch FlyingCarColors.GREY nicht zulässig. Das ist ein guter Schutz gegen Fehler, denn wenn der Compiler dies durchließe, könnte sich im Nachhinein die Belegung von GREY in BaseColors oder PlaneColors ohne Neuübersetzung aller Klassen ändern, und zu Schwierigkeiten führen. Diesen Fehler – die Oberschnittstellen haben für eine Konstante unterschiedliche Werte – müsste die Laufzeitumgebung erkennen. Doch das ist nicht möglich und in der Regel setzt der Compiler die Werte auch direkt in die Aufrufstelle ein und ein Zugriff auf die Konstantenwerte der Schnittstelle findet nicht mehr statt.

10. Auflage der Insel (Band 1) online

http://openbook.galileocomputing.de/javainsel/. Die 9. ist (noch) unter http://openbook.galileocomputing.de/javainsel9/ zu erreichen, sie hat teilweise noch mehr Details. Der 1. Band ist natürlich jetzt schmaler, weil der fortgeschrittene Stoff in den 2. Band (http://www.galileocomputing.de/2253?GPP=opjiX) gerutscht ist. Ob der 2. Band online sein wird, ist noch unklar, das hängt auch von den Lesern ab. (Beim Verlag anfragen.) Zwei (gute) Kapitel aus Band 2 sind als PDF verfügbar: http://www.galileocomputing.de/download/dateien/2624/galileocomputing_java_7_mehr_als_eine_insel.pdf mit Neues zu Java 7 und Reflection. Das sind mehr als 100 Seiten.

 

PS: Auch bei Google Plus https://plus.google.com/111898412819822416170/ verbeischauen.

Was ist JavaFX?

AWT und Swing sind bisher die Standardlösungen für grafische Anwendungen unter Java. AWT bildet das Fundament mit Ereignisbehandlung, Fenster-Management und einer mächtigen 2D-API. Swing sitzt auf dem AWT und ist eng mit ihm verbunden. Es realisiert die Komponenten, die zum Teil selbst in Java implementiert sind, manch ein Look and Feel – wie im Fall von Windows – lässt die Komponenten nativ vom Betriebssystem zeichnen.

Swing und AWT sind mächtig, aber es hat sich in den Letzen Jahren nicht großartig weiterentwickelt. Insbesondere gibt es Lücken im Bereich Medien und Animation, etwas, was bei modernen grafischen Oberflächen heutzutage gefragt ist. Statt das Sun/Oracle in die Weiterentwicklung investiert, hat sich das Unternehmen für eine komplette Neuentwicklung der GUI-Ebene entschieden, die nichts mehr mit Swing/AWT gemeinsam hat: JavaFX.

JavaFX ist eine Komplettlösung mit einer API für

· GUI-Komponenten

· HMTL/CSS/JavaScript mit eingebetteten Web-Brower

· Animationen

· Video

· Audio

· 2D und 3D

Da JavaFX komplett alle APIs für moderne Oberflächen anbietet, und auch nicht von AWT/Swing abhängig ist, bildet JavaFX einen kompletten Media-Stack. Die Betonung liegt auf Media, denn die AWT/Swing-API im Java SE kann keine Medien einbinden oder abspielen. Zwar ist JavaFX auch noch kein Teil der Java SE, doch das kann sich ändern. Über Profile sollte JavaFX auch auf mobilen Endgeräten und im Internet wie Applets laufen, allerdings ist eher davon auszugehen, das JavaFX es bei Rich-Client-Anwendungen Einzug hält und dort AWT/Swing verdrängt. Es ist nicht abzusehen, dass JavaFX im Internet als Flash-Ersatz oder auf mobilen Endgeräten punkten kann, dafür ist die Kombination HTML5 + CSS3 + JavaScript zu attraktiv.

Anders als AWT ist die JavaFX-Implementierung auf der Höhe der Zeit und greift direkt auf alle 2D/3D-Fähigkeiten moderner Grafikkarten zurück. So kann mit JavaFX alles das programmiert werden, was bisher eher mit Flash gemacht wurde, wohl aber fehlen noch die tollen Entwicklertools. Es gibt Plugins für Adobe Photoshop und Illustrator, mit denen Grafiken und Pfade exportiert werden können, aber eben keine ganzen Animationen, die etwa mit Adobe Flash erzeugt wurden. Und seit dem Adobe Flash auch HTML5 exportiert, öffnet sich eine ganz neue Welt.

 

Geschichte

JavaFX ist schon sehr lange in Entwicklung und viele interne Swing-Entwickler wurden auf das Projekt angesetzt – daran liegt es wohl auch, dass bei Swing nicht mehr passierte. Im Jahr 2003 wurde JavaFX dann auf der SunOne Konferenz vorgestellt, zusammen mit der Programmiersprache Java FX Script. Die Sprache macht es einfach möglich hierarchische Objektgrafen aufzubauen, und bot eine nette Syntax für Objekt-Bindung, doch wurde sie für die aktuelle Version JavaFX 2.0 fallen gelassen. Oracle wollte keine weitere Programmiersprache, sondern eine pure Java API, die sich von unterschiedlichen existierenden Skriptsprachen dann ansprechen kann. Das ist sicherlich eine gute Entscheidung, denn unter Groovy sieht das sehr schlank aus, fast wie mit JavaFX Script auch (http://groovy.codehaus.org/GroovyFX).

Microsoft Office-Dokumente in Java verarbeiten

Auch wenn sich die Welt nach freien und quelloffenen Lösungen sehnt, Microsoft Office ist immer noch eines der bestverkauftesten Produkte auf diesen Planeten mit dem MS-Dokumentenformat als Sonne im Mittepunkt. Da die Dokumentation von Microsofts Dateiformaten mittlerweile zugänglich[1] sind, gibt es auch Java-Bibliotheken, die MS-Office-Dokumente einlesen, modifizieren und schreiben. Bekannt dafür ist Apache POI[2] (http://poi.apache.org/), was APIs für folgende Formate (Komponenten genannt) bietet:

POI-Komponete

Aufgabe

Grad der Unterstützung

HSSF, XSSF

Excel XLS, Excel XLSX

Gut

HSLF, XSLF

PowerPoint PPT, PowerPoint PPTX

Ausreichend

HWPF, XWPF

Word DOC, Word DOCX

Befriedigend

HDGF

Visio VSD

Rudimentär, nur lesen

HPBF

Publisher PUB

Rudimentär, nur lesen

HMEF/HSMF

Outlook MSG/ Microsoft TNEF (Transport Neutral Encoding Format)

Rudimentär, nur lesen

OpenXML4J

Open Packaging Conventions (OPC)/OOXML

Gut

POIFS

OLE 2 Compound Document (OLE2 Filesystem)

Sehr gut

HPSF

OLE2 Property Sets

Sehr gut

POI-Komponenten nach http://poi.apache.org/overview.html#components

OpenXML4J und POIFS sind keine üblichen Dokumentenformate, aber Archivformate für Microsoft-Dokumenten (analog zu Zip). HPSF erlaubt Zugriff auf Datei-Metadaten wie Autor, Titel, usw.

Neben POI gibt es nicht mehr so viele Alternativen, eher kleinere Bibliotheken, die sich auf ein Formate spezialisiert haben. So etwa http://jexcelapi.sourceforge.net/ für Excel, was eine sehr einfache und intuitive API hat, aber beim Lesen oft Probleme bereitet. Nur wurde es bisher seit 2 Jahren nicht mehr aktualisiert. Eine kommerzielle Lösung bietet Aspose (http://www.aspose.com/categories/java-components/aspose.total-for-java/default.aspx). Für den Zugriff auf MS Access Datenbanken gibt es die quelloffene Bibliothek http://jackcess.sourceforge.net/, die auch eine sehr aktuelle fluent API hat. Auf MS Access lässt sich aber auch per ODBC zurückgreifen, das Datenbankkapitel gibt eine kleine Übersicht.

Das neue Office-Format basiert auf XML und so auch deutlich einfacher zu verarbeiten als das binäre Format. Es ist im Office Open XML beschrieben. Für Word-Dokumente gibt es zum Beispiel das relativ neue Projekt java2word (http://code.google.com/p/java2word/).

Neben dem Office Open gibt es das OASIS Open Document Format – mit Dateiformaten wie OpenDocument (ODF) für Textdokumente – die etwa von OpenOffice verarbeitet werden. Eine Bibliothek zum Verarbeiten bietet das ODF Toolkit (http://odftoolkit.org/).

 

Fehlt noch was?


[1] http://www.microsoft.com/interop/docs/officebinaryformats.mspx

[2] POI steht für „Poor Obfuscation Implementation“, weil das Dateiformat so kryptisch ist. Die Kürzel der Komponenten beginnen in der Regel mit „H“ was für „Horrible“ steht .

Deklarative JavaFX-Oberflächen mit FXML

Den Szene-Graph über Java Programmcode aufzubauen ist eine Möglichkeit, doch JavaFX erlaubt es auch, die Objekte über XML zu konfigurieren. Das erlaubt es viel einfacher, grafische Oberflächen über Gui-Builder aufzubauen und sauber das Layout (was) vom Code (wie) zu trennen.

Die hierarchische Struktur von XML passt natürlich prima zu der Hierarchie, die es bei grafischen Oberflächen gibt: Ein Fenster enthält Container, die wiederum Elemente enthalten, usw. Für unser kleines Beispiel soll eine Oberfläche drei Elemente bieten: Eine Beschriftung, ein Textfeld und eine Schaltfläche. Drückt der Anwender die Schaltfläche, soll der Text im Textfeld in Großbuchstaben konvertiert werden.

Die XML-Datei covert2UpperCase.fxml sieht so aus:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.layout.*?>
<?import javafx.scene.control.*?>

<HBox xmlns:fx="http://javafx.com/fxml"
      fx:controller="com.tutego.insel.javafx.ButtonController">
  <children>
    <Label text="Eingabe: " />
    <TextField fx:id="input" />
    <Button text="Konvertiere" onAction="#convertAction" />
  </children>
</HBox>

Die Hierarchie ist gut zu erkennen. Die interessanten Dinge sind andere:

  1. Zu Beginn gibt es eine Art import-Anweisung um Typnamen nicht voll qualifizieren zu müssen. Für den Rückgriff auf Grafiken muss <?import javafx.scene.image.*?> eingebunden werden und <?import javafx.scene.*?> für Group, Node oder ähnliches, was wir im Programm aber alles nicht nutzen.
  2. HBox hat zwei Attribute: Ein Attribut deklariert den Namensraum fx, ein anderes eine Klasse, den sogenannten Controller, der später die Ereignisbehandlung für den Klick übernimmt. Die Typen in FXML heißen genauso wie die Klassennamen. Natürlich könne auch eigene Klassen eingebaut werden, sofern sie mit <?import> bekannt gemacht wurden.
  3. Das TextField bekommt mit dem Attribut fx:id eine ID zugewiesen, unter der das Textfeld später erfragt werden kann. JavaFX geht noch einen Schritt weiter, und bildet das Objekt mit der ID automatisch auf ein Attribut der Controller-Klasse ab. Label und Schaltfläche brauchen keine IDs, da sie nicht erfragt werden müssen.
  4. Das Attribut onAction der Schaltfläche referenziert Programmcode, der immer aufgerufen wird, wenn die Schaltfläche gedrückt wird. Hier kann direkt Java-Quellcode stehen, oder, wie in unserem Fall, ein # und der Name einer Methode, die in einem Controller deklariert werden muss. Den Klassenamen vom Controller haben wir am Wurzelelement deklariert.

Die Ereignisbehandlung ist komplett aus der FXML-Datei rausgezogen und wandert in Controller-Klassen. Die eigene Klasse ButtonController, die voll qualifiziert bei fx:controller genannt wurde, enthält:

com/tutego/insel/javafx/ButtonController.java

package com.tutego.insel.javafx;

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.TextField;

public class ButtonController
{
  @FXML
  private TextField input;

  @FXML
  protected void convertAction( ActionEvent event )
  {
    input.setText( input.getText().trim().toUpperCase() );
  }
}

Drei Dinge fallen ins Auge:

  1. Die Controller-Klasse erweitert keine Schnittstelle.
  2. Die Annotation @FXML sagt, dass der Verweis auf das TextField-Objekt aus dem Szene-Graphen in die Variable input injiziert werden soll.
  3. Da in der FXML-Datei an der Schaltfläche ein onAction="#convertAction" steht, muss das zu einer Methode zugeordnet werden. Die Annotation @FXML an der Methode unter dem Namen convertAction stellt diese Beziehung her.

Das Hauptprogramm ist nun relativ einfach:

com/tutego/insel/javafx/FXMLDemo.java, start()

@Override
public void start( Stage stage ) throws Exception
{
  Parent p = FXMLLoader.load( getClass().getResource( "covert2UpperCase.fxml" ) );
  stage.setScene( new Scene( new Group( p ), 500, 400 ) );
  stage.setVisible( true );
}

Was noch alles mit FXML möglich ist, beschreibt ein Dokument von Oracle: http://fxexperience.com/wp-content/uploads/2011/08/Introducing-FXML.pdf.

Mein Buchbeispiel ist falsch (und nur einem fällt es auf und sagt es)

Das folgende Code für mein Semaphoren-Beispiel (http://openbook.galileodesign.de/javainsel7/javainsel_10_006.htm) habe ich vereinfacht und ist so nicht wirklich korrekt:

  1: static Runnable r = new Runnable() { 
  2:     public void run() { 
  3:       while ( true ) { 
  4:         try 
  5:         { 
  6:           semaphore.acquire(); 
  7:  
  8:           System.out.println( "Thread=" + Thread.currentThread().getName() + 
  9:                        ", Available Permits=" + semaphore.availablePermits() ); 
 10:           Thread.sleep( 2000 ); 
 11:         } 
 12:         catch ( InterruptedException e ) { 
 13:           e.printStackTrace(); 
 14:         } 
 15:         finally { 
 16:           semaphore.release(); 
 17:         } 
 18:       } 
 19:     } 
 20:   };

Wer findet den Fehler noch?

Nun, in der 10. Auflage ist der Fehler jedenfalls korrigiert und ein Dank geht an Marco Völz.

Aneinanderreihung von Comparatoren

Oftmals ist das Ordnungskriterium aus mehreren Bedingungen zusammengesetzt, wie die Sortierung in einem Telefonbuch zeigt. Erst gibt es eine Sortierung nach dem Nachnamen, dann folgt der Vorname. Um diese mit einem Compartor-Objekt zu lösen, müssen entweder alle Einzelvergleiche in ein neues Compartor-Objekt verpackt werden, oder einzelne Comparatoren zu einem „Super“-Comparator zusammengebunden werden – die zweite Lösung ist natürlich schöner, denn das erhöht die Wiederverwendbarkeit, denn einzelne Comparatoren können dann leicht für andere Zusammenhänge genutzt werden.

Comparatoren in eine Vergleichskette setzen

Am Anfang steht ein besonderer Comparator, der sich aus mehreren Comparatoren zusammensetzt. Immer dann, wenn ein Teil-Compartor bei zwei Objekten aussagt, dass sie gleich sind (der Vergleich liefert 0 ist), so soll der nächste Comparator die Endscheidung fällen – kann er das auch nicht, weil das Ergebnis wieder 0 ist, geht es zum nächsten Vergleicher.

Den Programmcode wollen wir einen neue Hilfsklasse ComparatorChain setzen:

package com.tutego.insel.util;

import java.util.*;

/**
 * A {@link Comparator} that puts one or more {@code Comparator}s in a sequence.
 * If a {@code Comparator} returns zero the next {@code Comparator} is taken.
 */
public class ComparatorChain<E> implements Comparator<E>
{
  private List<Comparator<E>> comparatorChain = new ArrayList<Comparator<E>>();
  
  /**
   * Construct a new comparator chain from the given {@code Comparator}s.
   * The argument is not allowed to be {@code null}.
   * @param comparators Sequence of {@code Comparator}s
   */
  @SafeVarargs  // ab Java 7
  public ComparatorChain( Comparator<E>... comparators )
  {
    if ( comparators == null )
      throw new IllegalArgumentException( "Argument is not allowed to be null" );

    Collections.addAll( comparatorChain, comparators );
  }

  /**
   * Adds a {@link Comparator} to the end of the chain.
   * The argument is not allowed to be {@code null}.
   * @param comparator {@code Comparator} to add
   */
  public void addComparator( Comparator<E> comparator )
  {
    if ( comparator == null )
      throw new IllegalArgumentException( "Argument is not allowed to be null" );

    comparatorChain.add( comparator );
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int compare( E o1, E o2 )
  {
    if ( comparatorChain.isEmpty() )
      throw new UnsupportedOperationException(
                  "Unable to compare without a Comparator in the chain" );

    for ( Comparator<E> comparator : comparatorChain )
    {
      int order = comparator.compare( o1, o2 );
      if ( order != 0 )
        return order;
    }

    return 0;
  }
}

Die ComparatorChain können wir auf zwei Weisen mit den Comparator-Gliedern füttern: einmal zu Initialisierungszeit im Konstruktor, und dann später noch über die addComparator()-Methode. Beim Weg über den Konstruktor ist ab Java 7 die Annotation @SafeVarargs zu nutzen, da sonst die Kombination eines Varargs und Generics auf der Nutzerseite zu einer Warnung führt.

Ist kein Comparator intern in der Liste, wird das compare() eine Ausnahme auslösen. Der erste Comparator in der Liste ist auch das Vergleichsobjekt was zuerst gefragt wird. Liefert er ein Ergebnis ungleich 0 liefert das die Rückgabe der compare()-Methode. Ein Ergebnis gleich 0 führt zur Anfrage des nächstes Comparators in der Liste.

Wir wollen diese ComparatorChain für ein Beispiel nutzen, dass eine Liste nach Nach- und Vornamen sortiert.

package com.tutego.insel.util;

import java.util.*;

public class ComparatorChainDemo
{
  public static class Person
  {
    public String firstname, lastname;

    public Person( String firstname, String lastname )
    {
      this.firstname = firstname;
      this.lastname  = lastname;
    }

    @Override public String toString()
    {
      return firstname + " " + lastname;
    }
  }

  public final static Comparator<Person>
    PERSON_FIRSTNAME_COMPARATOR = new Comparator<Person>() {
      @Override public int compare( Person p1, Person p2 ) {
        return p1.firstname.compareTo( p2.firstname );
      }
    };

  public final static Comparator<Person>
    PERSON_LASTNAME_COMPARATOR = new Comparator<Person>() {
      @Override public int compare( Person p1, Person p2 ) {
        return p1.lastname.compareTo( p2.lastname );
      }
    };

  public static void main( String[] args )
  {
    List<Person> persons = Arrays.asList(
      new Person( "Onkel", "Ogar" ), new Person( "Olga", "Ogar" ),
      new Person( "Peter", "Lustig" ), new Person( "Lara", "Lustig" ) );

    Collections.sort( persons, PERSON_LASTNAME_COMPARATOR );
    System.out.println( persons );

    Collections.sort( persons, PERSON_FIRSTNAME_COMPARATOR );
    System.out.println( persons );

    Collections.sort( persons, new ComparatorChain<Person>(
        PERSON_LASTNAME_COMPARATOR, PERSON_FIRSTNAME_COMPARATOR ) );
    System.out.println( persons );
  }
}

Die Ausgabe ist:

[Peter Lustig, Lara Lustig, Onkel Ogar, Olga Ogar]

[Lara Lustig, Olga Ogar, Onkel Ogar, Peter Lustig]

[Lara Lustig, Peter Lustig, Olga Ogar, Onkel Ogar]

Inselupdate: Ein Wort zu Microsoft, Java und zu J++, J#

In der Anfangszeit verursachte Microsoft einigen Wirbel um Java. Mit Visual J++ (gesprochen „Jay Plus Plus“) bot Microsoft schon früh einen eigenen Java-Compiler (Teil vom Microsoft Development Kit) und mit der Microsoft Java Virtual Machine (MSJVM) eine eigene schnelle Laufzeitumgebung. Das Problem war nur, dass Dinge wie RMI und JNI am absichtlich fehlten[1] – JNI wurde 1998 nachgereicht. Entgegen alle Standards führte der J++-Compiler neue Schlüsselwörter multicast und delegate ein. Weiterhin fügte Microsoft einige neue Methoden und Eigenschaften hinzu, zum Beispiel J/Direct, um der plattformunabhängigen Programmiersprache den Windows-Stempel zu verpassen. Mit J/Direct konnten Programmierer aus Java heraus direkt auf Funktionen aus dem Win32-API zugreifen und damit reine Windows-Programme in Java programmieren. Durch Integration von DirectX soll die Internet-Programmiersprache Java multimediafähig gemacht werden. Das führte natürlich zu dem Problem, dass Applikationen, die mit J++ erstellt wurden, nicht zwangsläufig auf anderen Plattformen lauffähig sind waren. Sun klagte gegen Microsoft.

Da es Sun in der Vergangenheit finanziell nicht besonders gut ging, pumpte Microsoft im April 2004 satte 1,6 Milliarden US$ in die Firma. Microsoft erkaufte sich damit das Ende der Kartellprobleme und Patentstreitigkeiten. Dass es bis zu dieser Einigung nicht einfach gewesen war, zeigen Aussagen von Microsoft-Projektleiter Ben Slivka über das JDK beziehungsweise die Java Foundation Classes, man müsse sie »bei jeder sich bietenden Gelegenheit anpissen« (»pissing on at every opportunity«).[2]

Im Januar 2004 beendete die Arbeit an Microsoft J++, denn die Energie floss in das .NET-Framework und der .NET-Sprachen. Am Anfang gab es mit J# eine Java-Version, die Java-Programme auf der Microsoft .NET-Laufzeitumgebungen CLR ausführt, doch Anfang 2007 wurde auch J# eingestellt. Das freie IKVM.NET (http://www.ikvm.net/) ist eine JVM für .NET und kommt mit einem Übersetzter von Java-Bytecode nach .NET-Bytecode, was es möglich macht, Java-Programme unter .NET zu nutzen. Das ist praktisch, denn für Java gibt es eine riesige Anzahl von Programmen, die somit auch für .NET-Entwickler zugänglich sind.

Microsoft hat sich aus der Java-Entwicklung nahezu vollständig zurückgezogen. Es gibt zum Beispiel noch den Microsoft JDBC Driver for SQL Server und Microsoft unterstützt eine API für Office-Dokumente. Das Verhältnis ist heute auch deutlich entspannter und vielleicht gratuliert Microsoft wie es auch Linux zum 20 Geburtstag gratuliert hat[3] irgendwann einmal Oracle.


[1] http://www.microsoft.com/presspass/legal/charles.mspx

[2] Würden wir nicht gerade im westlichen Kulturkreis leben, wäre diese Geste auch nicht zwangsläufig unappetitlich. Im alten Mesopotamien steht »pissing on« für »anbeten«. Da jedoch die E-Mail nicht aus dem Zweistromland kam, bleibt die wahre Bedeutung wohl unserer Fantasie überlassen.

[3] http://www.youtube.com/watch?v=ZA2kqAIOoZM

Aufrufstack von Ausnahmen verändern

Wenn wir in einer Ausnahmebehandlung eine Exception e auffangen und genau diese dann mit throw e weiterleiten, müssen wir uns bewusst sein, dass die Ausnahme e auch den Aufrufstack weitergibt. Aus dem vorangehenden Beispiel:

java.net.URISyntaxException: Malformed escape pair at index 7: http://%

at java.net.URI$Parser.fail(URI.java:2827)

at java.net.URI$Parser.scanEscape(URI.java:2957)

at java.net.URI.<init>(URI.java:595)

at Rethrow.createUriFromHost(Rethrow.java:9)

at Rethrow.main(Rethrow.java:24)

Die main()-Methode fängt den Fehler von createUriFromHost() ab, aber diese Methode steht nicht ganz oben im Aufrufstack. Die Ausnahme stammte ja gar nicht von createUriFromHost() selbst, sondern von fail(), sodass fail() oben steht. Ist das nicht gewünscht, kann es korrigiert werden, denn die Basisklasse für alle Ausnahmen Throwable bietet die Methode fillInStackTrace(), mit dem sich der Aufrufstack neu füllen lässt. Unsere bekannte Methode createUriFromHost() soll auf fillInStackTrace() zurückgreifen:

public static URI createUriFromHost( String host ) throws URISyntaxException

{

try

{

return new URI( "http://" + host );

}

catch ( URISyntaxException e )

{

System.err.println( "Hilfe! " + e.getMessage() );

e.fillInStackTrace();

throw e;

}

}

Kommt es in createUriFromHost() zur URISyntaxException, so fängt unsere Methode diese ab. Ursprünglich ist in e in der Aufrufstack mit der fail()-Methode ganz oben gespeichert, allerdings löscht fillInStackTrace() zunächst den ganzen Stracktrace und füllt ihn neu mit dem Pfad, den der aktuelle Thread zu der der Methode führt, die fillInStackTrace() aufruft – das ist createUriFromHost(). Daher beginnt die Konsolenausgabe auch mit unserer Methode:

Hilfe! Malformed escape pair at index 7: http://%

java.net.URISyntaxException: Malformed escape pair at index 7: http://%

at RethrowWithFillInStackTrace.createUriFromHost(RethrowWithFillInStackTrace.java:14)

at RethrowWithFillInStackTrace.main(RethrowWithFillInStackTrace.java:24)

JComboxBox mit Separator nutzen

Standardmäßig unterstützt die JList und auch JComboBox keine Separatoren, doch die Unterteilung in Segmente ist in der Praxis sehr nützlich. Microsoft Word und PowerPoint benutzt sie zum Beispiel, um die zuletzt vom Benutzer ausgewählten Zeichensätze prominent oben in der Liste zu haben (Excel dagegen nicht).

Wir wollen diese Möglichkeit nachbilden, und dabei noch einiges über Modelle und Renderer lernen.

Weiterlesen

null/NullPointerException in anderen Programmiersprachen: Ruby, nil, Scala, Option, None, Some

Ist Java eine pure objektorientiere Programmiersprache? Nein, da Java einen Unterschied zwischen primitiven Typen und Referenztypen macht. Nehmen wir für einen Moment an, dass es primitive Typen nicht gibt. Wäre Java dann eine reine objektorientierte Programmiersprache, bei der jede Referenz ein pures Objekt referenziert? Die Antwort ist immer noch nein, da es mit null etwas gibt, mit dem Referenzvariablen initialisiert werden können, aber was kein Objekt repräsentiert und keine Methoden besitzt. Und das kann bei der Dereferenzierung eine NullPointerException geben.

Neue Programmiersprachen gehen da etwas andere Wege und null-Referenzierungen sind nicht möglich. In der Sprache Ruby zum Beispiel ist immer alles ein Objekt. Wo Java mit null ein „nicht belegt“ ausdrückt, macht das Ruby mit nil. Der feine Unterschied ist, dass nil ein Exemplar der Klasse NilClass ist, genau genommen ein Singleton, was es im System nur einmal gibt. nil hat auch ein paar öffentliche Methoden wie to_s (wie Javas toString()), was dann einen leeren String liefert. Mit nil gibt es keine NullPointerException mehr, aber natürlich immer noch einen Fehler, wenn auf diesem Objekt vom Typ NilClass eine Methode aufgerufen wird, die es nicht gibt.

In Objective-C, der Standardsprache für iPhone-Programme, gibt es das Null-Objekt nil. Üblicherweise passiert nicht, wenn eine Nachricht an das nil-Objekt gesendet wird; die Nachricht wird einfach ignoriert.[1] Es gibt Programmiersprachen, die noch etwas anders an das null-Problem herangehen. Bei der JVM-Sprache Scala gibt es für den Wert null einen eigenen Typ, nämlich Null. (Null erbt von AnyRef – was wiederum von Any erbt, dem absoluten Basistyp – und Null hat sogar mit Nothing einen Untertyp). Allerdings wird Scalas null nicht so wie Rubys null genutzt sondern entspricht Java null: Der Versuch, eine genullte Variable zu dereferenzieren, führt zu einer NullPointerException. Anders gesagt: Scala führt für null einen Typ ein, aber eine NullPointerException ist immer noch nicht passé. Statt aber in Scala null zurückzugeben, nutzen Programmierer etwas anderes: eine Option. Die Klasse Option repräsentiert optionale Werte und kommt in zwei Ausprägungen: Some und None (entspricht Rubys nil). Statt dass wie in Java eine Methoden eine Referenz direkt zurückgibt, geben Scala-Methoden etwas vom Typ Option zurück, und das heißt, entweder ein None- oder Some-Objekt. Das Some-Objekt ist ein Behälter und speichert die eigentliche Referenz. Eine Methode mit Option-Rückgabe sieht etwa so aus: if ( WertUngleichNull ) Some(Wert) else None. Mit dem parametrisierten Konstruktor Some(Wert) kommt der Wert in das Some-Objekt. Empfängt der Aufrufer die Rückgabe, kann er am Typ erkennen, ob die Operation erfolgreich war oder nicht. Das ist jedoch nicht, wie eine Option üblicherweise in Scala eingesetzt wird. Denn Option-Objekte (und somit Some oder None) besitzen eine Methode getOrElse(), sodass sich für eine Rückgabe result vom Typ Option schreiben lässt: result.getOrElse(alternativerWert). Repräsentiert result ein Some-Objekt, so liefert die Implementierung von getOrElse() den gespeicherten Wert, ist es None, so liefert die Implementierung das Argument als Rückgabe. Spontan hört sich das jetzt noch nicht so wahnsinnig innovativ an, aber interessant wird die Option durch die Tatsache, dass Scala sie an verschiedenen Stellen intern in den Klassenbibliotheken einsetzt. Das liegt dran, dass die Option-Klasse an die 30 Methoden deklariert und viele der Methoden etwa bei Operationen auf Datenstrukturen aufgerufen werden. Wenn zum Beispiel auf allen Elementen der Liste eine Transformation durchgeführt werden soll, und ein None-Objekt befindet sich in der Liste, passiert beim Aufruf der Transformationsmethoden auf dem None-Objekt nichts, denn dort ist die Implementierung einfach leer.


[1] Es gibt auch Compiler wie der GCC, der mit der Option -fno-nil-receivers dieses Verhalten abschaltet, um schnellere Maschinencode zu erzeugen. Denn letztendlich muss in Maschinencode immer ein Test stehen, der auf 0 prüft.

Die ersten Schritte mit JAXB

JAXB ist eine API zum Übertragen von Objektzuständen auf XML-Dokumente und umgedreht. Anders als eine manuelle Abbildung von Java-Objekten auf XML-Dokumente oder das Parsen von XML-Strukturen und Übertragen der XML-Elemente auf Geschäftsobjekte arbeitet JAXB automatisch. Die Übertragungsregeln definieren Annotationen, die Entwickler selbst an die JavaBeans setzten können, aber JavaBeans werden gleich zusammen mit den Annotationen von einem Werkzeug aus deiner XML-Schema-Datei generiert.

Java 6 integriert JAXB 2.0, und das JDK 6 Update 4 – sehr ungewöhnlich für ein Update – aktualisiert auf JAXB 2.1.

1.1.1          Bean für JAXB aufbauen

Wir wollen einen Player deklarieren, und JAXB soll ihn anschließend in ein XML-Dokument übertragen.

com/tutego/insel/xml/jaxb/Player.java, Player

@XmlRootElement

class Player

{

private String name;

private Date   birthday;

public String getName()

{

return name;

}

public void setName( String name )

{

this.name = name;

}

public void setBirthday( Date birthday )

{

this.birthday = birthday;

}

public Date getBirthday()

{

return birthday;

}

}

Die Klassen-Annotation @XmlRootElement ist an der JavaBean nötig, wenn die Klasse das Wurzelelement eines XML-Baums bildet. Die Annotation stammt aus dem Paket javax.xml.bind.annotation.

1.1.2          JAXBContext und die Marshaller

Ein kleines Testprogramm baut eine Person auf bildet sie dann in XML ab – die Ausgabe der Abbildung kommt auf dem Bildschirm.

com/tutego/insel/xml/xml/jaxb/PlayerMarshaller.java, main()

Player johnPeel = new Player();

johnPeel.setName( „John Peel“ );

johnPeel.setBirthday( new GregorianCalendar(1939,Calendar.AUGUST,30).getTime() );

JAXBContext context = JAXBContext.newInstance( Player.class );

Marshaller m = context.createMarshaller();

m.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE );

m.marshal( johnPeel, System.out );

Nach dem Lauf kommt auf den Schirm:

<?xml version=“1.0″ encoding=“UTF-8″ standalone=“yes“?>

<player>

<birthday>1939-08-30T00:00:00+01:00</birthday>

<name>John Peel</name>

</player>

Alles bei JAXB beginnt mit der zentralen Klasse JAXBContext. Die statische Methode JAXBContext.newInstance() erwartet standardmäßig eine Aufzählung der Klassen, die JAXB behandeln soll. Der JAXBContext erzeugt den Marshaller zum Schreiben und Unmarshaller zum Lesen. Die Fabrikmethode createMarshaller() liefert einen Schreiberling, der mit marshal() das Wurzelobjekt in einen Datenstrom schreibt. Das zweite Argument von marshal() ist unter anderem ein OutputStream (wie System.out in unserem Beispiel), Writer oder File-Objekt.

JAXB beachtet standardmäßig alle Bean-Properties, also birthday und name, und nennt die XML-Elemente nach den Properties.

1.1.3          Ganze Objektgrafen schreiben und lesen

JAXB bildet nicht nur das zu schreiben Objekt ab, sondern auch rekursiv alle referenzierten Unterobjekte. Wir wollen den Spieler dazu in einen Raum setzen und den Raum in XML abbilden. Dazu muss der Raum die Annotation @XmlRootElement bekommen und bei Player kann sie entfernt werden, wenn nur der Raum selbst aber kleine Player als Wurzelobjekte zum Marshaller kommen.

com/tutego/insel/xml/xml/jaxb/Room.java, Room

@XmlRootElement( namespace = „http://tutego.com/“ )

public class Room

{

private List<Player> players = new ArrayList<Player>();

@XmlElement( name = „player“ )

public List<Player> getPlayers()

{

return players;

}

public void setPlayers( List<Player> players )

{

this.players = players;

}

}

Zwei Annotationen kommen vor: Da Room der Start des Objektgrafen ist, trägt es @XmlRootElement. Als Erweiterung ist das Element namespace für den Namensraum gesetzt, da bei eigenen XML-Dokumenten immer ein Namensraum genutzt werden soll. Weiterhin ist eine Annotation @XmlElement am Getter getPlayers() platziert, um den Namen des XML-Elements zu überschreiben, damit das XML-Element nicht <players> heißt, sondern <player>.

Kommen wir abschließend zu einem Beispiel, das einen Raum mit 2 Spielern aufbaut, und diesen Raum dann in eine XML-Datei schreibt. Statt allerdings JAXBContext direkt zu nutzen und einen Marshaller zum Schreiben und Unmarshaller zum Lesen zu erfragen, kommt im zweiten Beispiel die Utility-Klasse JAXB zum Einsatz, die ausschließlich statische überladene marshal() und unmarshal() Methoden anbietet.

Player john = new Player();

john.setName( „John Peel“ );

Player tweet = new Player();

tweet.setName( „Zwitscher Zoe“ );

Room room = new Room();

room.setPlayers( Arrays.asList( john, tweet ) );

File file = File.createTempFile( „room-jaxb-„, „.xml“ );

JAXB.marshal( room, file );

Room room2 = JAXB.unmarshal( file, Room.class );

System.out.println( room2.getPlayers().get( 0 ).getName() ); // John Peel

file.deleteOnExit();

Falls etwas beim Schreiben oder Lesen misslingt, werden die vorher geprüften Ausnahmen in einer DataBindingException ummantelt, die eine RuntimeException ist.

Die Ausgabe ist:

<?xml version=“1.0″ encoding=“UTF-8″ standalone=“yes“?>

<ns2:room xmlns:ns2=“http://tutego.com/“>

<player>

<name>John Peel</name>

</player>

<player>

<name>Zwitscher Zoe</name>

</player>

</ns2:room>

Da beim Spieler das Geburtsdatum nicht gesetzt war (null wird referenziert), wird es auch nicht in XML abgebildet.