Erste Spezifikation der neuen Java 7 Closures

Die gibt es unter http://mail.openjdk.java.net/pipermail/lambda-dev/2010-January/000349.html. Das ganze heißt “Projekt Lambda”. Vom ersten Überfliegen her wirklich eine starke Vereinfachung der ersten BGGA-Closures Variante und ohne große Überraschungen.

Labels:

Frustrierend: Java 7 doch später

Heute auf der Devoxx Konferenz gab es die Info, dass wegen der neuen Spracheigenschaften sich Java 7 von Februar auf etwa September verschieben wird. Immerhin wird es dann wohl Closures geben und das Projekt Coin wird vermutlich noch mehr aufnehmen, etwa die Aufzählung von mehreren Exceptions im catch-Block.

Labels:

Überraschung: Wohl doch Closures in Java 7

Fork und Join aus Java 7

Gesucht ist ein Framework zum Lösen von parallelen D&C-Algorithmen, die berechnungsintensiv sind. Sun hat in Java 7 das Fork/Join integriert, was im Rahmen von jsr166y (http://gee.cs.oswego.edu/dl/concurrency-interest/) unter maßgeblicher Arbeit von Doug Lea entwickelt wurde. Die grundlegende Idee ist, neben Threads, noch eine andere Arbeitseinheit einzuführen, die Tasks.
  • Threads: Werden vom Betriebssystem verwaltet und laufen entweder pseudo-parallel auf einem Prozessor/Core oder echt parallel. Threads können sich mit anderen Threads koordinieren. Zu viele Threads, die sich im Weg stehen und aufeinander Warten führen zu keiner verbesserten Ausführungszeit gegenüber einer sequentiellen Lösung.
  • Tasks: Werden von Threads bzw. einem Thread-Pool ausgeführt. Sie sind Arbeitseinheiten, die nicht auf andere Tasks warten

Die Tasks sind kleine Arbeitspakete und werden in eine Task-Queue gelegt und dann von Threads abgearbeitet. Hat das System zwei Prozessoren und hat der Thread-Pool die Größe 2, so ist es wahrscheinlich, dass 2 Tasks parallel abgearbeitet werden. Gibt es 4 Prozessoren, können 4 Tasks vielleicht parallel laufen. Tasks lassen sich also grundsätzlich auf einer beliebigen Anzahl Threads und somit Prozessoren/Cores bringen, wobei im Gegensatz die Effektivität von Threads immer mit der physikalischen Anzahl von Prozessoren/Cores assoziiert ist.

Das Fork/Join-Framework löst die Probleme effektiv. Wie der Name schon andeutet, geht es bei Fork um das Erstellen eines neuen Tasks und bei Join um das Zusammenführen der Ergebnisse. Die Fork/Join-Bibliothek bietet dazu die Klasse ForkJoinPool und zwei zentrale Methoden: fork() und join(). Zur Abarbeitung der Tasks stellt das Framework die Threads zu Verfügung, deren Anzahl wir zwar selbst bestimmen können, aber die Anzahl Prozessoren/Cores eine gute Standardgröße ist.[1] Die Methode fork() erzeugt einen neuen Task, der an den Anfang (!) einer Queue gestellt wird. Dabei haben alle Threads eine Queue für ihre Arbeitsaufträge und sollte einmal eine Queue leer gelaufen sein, so nimmt sich der Thread einfach einen Task vom Ende (!) einer anderen nicht-leeren Queue. (Das nennt sich work-stealing und ist in der Realwelt ziemlich selten anzutreffen.) Dass neue Tasks an den Anfang gestellt werden ist einfach zu erklären: Die Tasks werden ja immer kleiner und somit stehen die kleinen, schnell lösbaren Aufgaben vorne. Erst später folgen die größeren Aufgaben, die auf die Ergebnisse der kleinen Aufgaben zurückgreifen, die dann logischerweise schon berechnet wurden.

Zur Theorie ein Beispiel: Es geht darum, mit Fork/Join ein Programm zu haben, welches parallel das Maximum eines Arrays sucht. Der Start ist:

int[] array = { 0, 9, 10, 111, 1, 12, 13, 14, 17 };
System.out.println( MaxElementInArrayFinder.findMax( array ) );

Die eigene Klasse MaxElementInArrayFinder bietet die Methode findMax(), die auf den ForkJoinPool zurückgreift, um mit invoke() den Haupt-Task abzusetzen.

class MaxElementInArrayFinder
{
private static final ForkJoinPool fjPool = new ForkJoinPool();
...
public static int findMax( int[] array )
{
return fjPool.invoke( new MaxElemTask( array, 0, array.length -1 ) );
}
}

Die Klasse MaxElemTask repräsentiert unser Arbeitspakt. Die Tasks referenzieren jeweils das Array, und die Anfangs-/Endeposition, ab der sie nach dem Maximum suchen sollen.

private static class MaxElemTask extends RecursiveTask<Integer>
{
private final int[] array;
private final int start, end;
MaxElemTask( int[] array, int start, int end )
{
assert array != null && start >= 0 && start <= end;
this.array = array;
this.start = start;
this.end = end;
}
@Override protected Integer compute()
{

}
}

Unsere Klasse erweitert die Basisklasse RecursiveTask<Integer> und deutet durch den generischen Typ schon an, das das Ergebnis des Tasks eine Ganzzahl sein wird, nämlich das Feldmaximum aus dem gewünschten Bereich. Der Konstruktor sichert die Werte und compute() führt die eigentliche Arbeit aus: Es löst entweder das Problem direkt, wenn es klein genug ist, oder spannt Unter-Tasks auf und wartet anschließend auf deren Ergebnisse.

@Override protected Integer compute()
{
assert array != null && array.length > 0;
System.out.printf( "max( start=%d, end=%d )%n", start, end );
if ( end - start < 4 )
{
int max = array[start];
for ( int i = start + 1; i <= end; i++ )
if ( array[i] > max )
max = array[i];
return max;
}
int middle = (start + end) / 2;
MaxElemTask leftTask = new MaxElemTask( array, start, middle );
leftTask.fork();
MaxElemTask rightTask = new MaxElemTask( array, middle + 1, end );
int rightMax = rightTask.compute();
int leftMax = leftTask.join();
return Math.max( rightMax, leftMax );
}

[1] Das die Maximalanzahl von Threads beim ForkJoinPool zurzeit 32767 ist, dürfte für normale Nutzer keine Einschränkung sein.

Labels: ,

switch auf Strings seit Java 7

Seit Java 7 sind switch-Anweisungen auf String-Objekten möglich.

String input = javax.swing.JOptionPane.showInputDialog( "Eingabe" );

switch ( input.toLowerCase() )

{

case "kekse":

System.out.println( "Ich mag Keeeekse" );

break;

case "kuchen":

System.out.println( "Ich mag Kuchen" );

break;

case "scholokade":

case "lakritze":

System.out.println( "Hm. lecker" );

break;

default:

System.out.printf( "Kann man %s essen?", input );

}

Obwohl Zeichenkettenvergleiche nun möglich sind, fallen Überprüfungen auf reguläre Ausdrücke leider heraus, die insbesondere Skriptsprachen anbieten.

Labels: ,

Java 7 Milestone 5: Build b76 vorgestellt

Download gibt es unter http://download.java.net/jdk7/m5/, die Neuerungen bei http://download.java.net/jdk7/changes/jdk7-b76.html. Am Interessantesten dürfen sein:

  • bb3. 6865582. jsr166y - jsr166 maintenance update
  • 6865571. Add a lightweight task framework known as ForkJoin
  • 6445158. Phaser - an improved CyclicBarrier
  • 6865579. Add TransferQueue/LinkedTransferQueue

und

Labels:

Swing-Komponenten neu erstellen oder verändern und JLayer

Zum Aufbau neuer Swing-Komponenten kommen eine Reihe von Möglichkeiten in Frage. Wenn passend, lässt sich eine existierende Swing-Komponente als Basisklasse nehmen und um nötige Eigenschaften erweitern, sofern die Basisklassen diese Möglichkeit im Grunde schon bieten. Soll etwa ein Texteingabefeld nur IP-Adressen zulassen, so ist dafür keine völlig neue Textkomponentenimplementierung nötig, sondern nur eine Unterklasse der Standard-Komponente mit passendem Dokumenten-Modell. Oder soll ein Liste nur Kontrollkästen (mit Text) darstellen soll, ist das schon über die JList mit passendem Renderer und Modell möglich.

Unproblematisch ist auch, wenn sich neue Komponenten aus anderen Swing Teilkomponenten zusammenzusetzen lassen und. Dann erweitert die neue Swing-Klasse erweitert einen Container wie JPanel, der einfach die anderen Elemente wie gewünscht platziert. Möglich ist dies zum Beispiel bei einer Statuszeile, da diese nichts großartiges macht, als einfach horizontal andere Komponenten anzuordnen und einen besonderen Rahmen zu setzen. Einen Dialog zur Auswahl eines Zeichensatzes bietet Swing bisher auch nicht an, der lässt sich aber als JDialog mit passenden Swing-Komponenten leicht nachbauen.

Mehr Arbeit ist nötig, wenn sich auf keine allgemeinen Swing-Komponenten zurückgreifen lässt. Die Swing-Bibliothek bietet etwa keine Ribbon-Komponente, keinen wirklich guten HTML-Renderer, oder ein Docking-Framework. Bei Anforderungen dieser Art lässt sich nicht so einfach auf Standardkomponenten zurückgreifen, sondern spezieller Programmcode zum Zeichnen nötig. Der wesentliche Unterschied ist also der, dass sich die Darstellung nicht vollständig an Standardkomponenten delegieren lässt sondern immer etwas einer Java-Code zum Zeichnen nötig ist.
Um es richtig gut zu machen, sind für eine eigene Swing-Komponente drei Dinge nötig: Die Komponentenklasse, eine Modellklasse und ein UI-Delegate. Die Komponentenklasse ist die Hauptklasse und eine JComponent, die der Entwickler auf die Gui setzt. Sie bietet die API zum Setzten der Zustände. Die Modell-Daten werden nicht selbst in der Komponentenklasse gespeichert, sondern idealweise über eine eigene Klasse modelliert. Die Tabelle nimmt zum Beispiel die Zellen aus einem Tabellemodell, eine Textkomponenten den Text aus einem Dokumentenmodell. Als letztes bleibt der UI-Delegate, der das wirkliche Zeichen und die Ereignisbehandlung übernimmt. Es kann sehr anspruchvoll sein ein gutes Aussehen und effektive Navigation zu erreichen und insbesondere wenn die Komponente in verschiedenen Look-and-Feels arbeiten soll, eine Menge Arbeit werden. Und das die eigene Swing-Komponente die UI-Eigenschaften wie Farben, Abstände und Antialiasing-Modus toleriert ist selbstverständlich.

Überlagerungen mit dem Swing-Komponenten-Dekorator JLayer

Können Swing-Komponten überlagert werden, können dadurch interessante Effekte erzieht werden. Ein paar Beispiele:

·    Während ein Text in die Textbox geladen wird, erscheint ein JProgressBar.
·    Bei aufwändigen Operationen wird das Haupt-Panel gesperrt und eine drehende Sanduhr erscheint.
·    Ist die Eingabe in einem Textfeld falsch, erscheint ein kleines Symbol, welches über die invalide Eingabe informiert.
·    Über einer leeren Tabelle liegt eine Beschriftung, die erklärt, dass Doppelklick eine neue Zeile einfügt.

Alle die Darstellungen lassen sich mit Hilfe der in Java 7 eingefügten Klassen JLayer einfach lösen (Nutzer vor Java 7 greifen auf SwingX zurück, denn von dort kommt die Klasse auch; sie heißt nur dort JXLayer. Als Alternative haben Autoren auch oft auf Glass-Pane zurückgegriffen.)
Haupteigenschaft von JLayer ist, sich um existierende Swing-Komponenten zu legen. Soll ein JLayer um ein Textfeld gelegt werden, heißt es:

JLayer layer = new JLayer( textField );
 

Die zu ummantelnde Komponente wird über den Konstruktor angegeben und nicht über add(), da JLayer kein Container ist. Der nächste Schritt ist die Angabe eines Objekts, dass das Zeichnen übernimmt.

layer.setUI( layerUI );

Die Angabe erfordert ein LayerUI-Objekt, welches eine paint()-Methode realisiert. Die Implementierung kann super.paint() aufrufen, um die ummantelte Komponente zu zeichnen, und dann eigenen Programmcode hinzufügen, um etwa einen Sanduhr darzustellen.
Das folgende Beispiel fasst die Schritte zusammen und realisiert ein Programm, welches bei Eingabe von „pu“ einen kleinen roten Kreis anzeigt.

JFrame f = new JFrame();
f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
f.setLayout( new BorderLayout(2, 2) );

f.add( new JSeparator(), BorderLayout.PAGE_START );
f.add( new JLabel( "Name:" ), BorderLayout.LINE_START );

final JTextField textField = new JTextField();

LayerUI layerUI = new LayerUI()
{
  @Override
  public void paint( Graphics g, JComponent component )
  {
    super.paint( g, component );

    if ( textField.getText().equalsIgnoreCase( "pu" ) )
    {
      g.setColor( new Color( 255, 0, 0, 100 ) );
      g.fillOval( 0, component.getHeight() - 10, 10, 10 );
    }
  }
};

JLayer layer = new JLayer( textField );
layer.setUI( layerUI );
f.add( layer );
f.add( new JSeparator(), BorderLayout.PAGE_END );

f.pack();
f.setVisible( true );


Die JLayer kann auch das Hauptpanel dekorieren und die Events auffangen. Das ist eine zweites Anwendungsfeld neben dem Änderung der Darstellung. Die JLayer-Komponente kann einfach Events auffangen und verarbeiten und so zum Beispiel global F1 für die Hilfe abfangen.

Labels: ,

Process-Ströme in Dateien umlenken

Ist der Unterprozess über start() gestartet, lassen sich über das Process-Objekt die Ein-/Ausgabe-Datenströme erfragen. Die Process-Klasse bietet getInputStream(), mit dem wir an genau die Daten kommen, die der externe Prozess in seinen Ausgabestrom schreibt, denn sein Ausgabestrom ist unser Eingebestrom, den wir konsumieren können. Auch ist getErrorStream() ein InputStream, denn das, was die externe Anwendung in den Fehlerkanal schreibt, empfangen wir in einem Eingabestrom. Mit getOutputStream() bekommen wir einen OutputStream, dass das externe Programm mit Daten füttert. Dies ist der Pipe-Modus, sodass wir einfach mit externen Programmen Daten austauschen können.
Neben diesem Pipe-Modus gibt es seit Java 7 eine Alternative, die Ströme direkt auf Dateien umzulenken. Dazu definiert die ProcessBuilder-Klasse diverse redirectXXX()-Methoden. (Sollte dann ein getXXXStream()-Aufruf gemacht werden, so kommen nicht-aktive Ströme zurück, denn das externe Programm kommuniziert ja dann direkt mit einer Datei und die Java-Pipe hängt nicht dazwischen.)


class java.lang.ProcessBuilder   

§    ProcessBuilder redirectInput( File file )
ProcessBuilder redirectInput( ProcessBuilder.Redirect source )
Der Unterprozess wird die Eingaben aus der angegeben Quelle beziehen.
§    ProcessBuilder redirectOutput( File file )
§    ProcessBuilder redirectOutput( ProcessBuilder.Redirect destination )
Der Unterprozess wird Standardausgaben an das angegebene Ziel senden.
§    ProcessBuilder redirectError( File file )
§    ProcessBuilder redirectError( ProcessBuilder.Redirect destination )
Der Unterprozess wird Fehlerausgaben an das angegebene Ziel senden.

Die redirectXXX(File file)-Methoden bekommen als Ziel ein einfaches File-Objekt. Die redirectXXX()-Methoden sind aber überladen mit einem anderen Typ Redirect, der als innere statische Klasse in ProcessBuilder angelegt ist. Mit Redirect.PIPE und Redirect.INHERIT gibt es zwei Konstanten, und drei statischen Methoden Redirect.from(File), Redirect.to(File), Redirect.appendTo(File) die Redirect-Objekte für die Umleitung zur Datei liefern. Die mit File parametrisierten Methoden greifen auf die Redirect-Klasse zurück, so dass es bei redirectOutput(File file) intern auf ein redirectOutput(Redirect.to(file)) herausläuft.

Labels: ,

NumberFormat, Währungen angeben und die Klasse Currency

Die NumberFormat-Klasse liefert mit getCurrencyInstance() einen Format-Objekt, welches neben der Dezimalzahl auch noch ein Währungssymbol mit anzeigt. So liefert NumberFormat.getCurrencyInstance().format(12345.6789) dann 12.345,68 €, also automatisch mit einem Euro-Zeichen. Dass es ein Euro-Zeichen ist, und kein Yen-Symbol liegt einfach daran, dass Java standardmäßig das eingestellte Land „sieht“ und daraus die Währung ableitet.  Wenn wir explizit den Formater mit einem Land initialisieren, etwa wie in

NumberFormat frmt1 = DecimalFormat.getCurrencyInstance( Locale.FRANCE );
System.out.println( frmt1.format( 12345.6789 ) );         // 12 345,68 €

so ist die Währung automatisch Euro (denn Frankreich nutzt den Euro); schreiben wir DecimalFormat.getCurrencyInstance(Locale.JAPAN) ist sie Yen und wir bekommen ¥12,346. (Es gibt standardmäßig keine Nachkommastellen beim Yen.) Locale-Objekten repräsentieren immer eine sprachliche Region.

DecimalFormat bzw. schon die Oberklasse NumberFormat ermöglicht die explizite Angabe der Währung. In der Java-Bibliothek wird sich durch die Klasse java.util.Currency repräsentiert. NumberFormat liefert mit getCurrency() die eingestellte Currency, die zur Formatierung verwendet wird und setCurrency() setzt sie neu. Das löst Szenarios, in denen etwa ein Euro-Zeichen die Währung darstellt, aber die Zahlenformatierung englisch ist, wie die folgenden Zeilen zeigen:

NumberFormat frmt = DecimalFormat.getCurrencyInstance( Locale.ENGLISH );
frmt.setCurrency( Currency.getInstance( "EUR" ) );
System.out.println( frmt.format( 12345.6789 ) );  // EUR12,345.68

Die Currency-Klasse bietet drei statische Methoden, die Currency-Objekte liefern. Da ist einmal getAvailableCurrencies(), was ein Set liefert und die beiden Fabrikfunktion getInstance(Locale locale) und getInstance(String currencyCode). Currency-Objekte besitzen eine ganze Reihe von Objektfunktionen, die etwa den ISO 4217 Währenscode liefen oder den ausgeschriebenen Währungsnamen (und das auch noch in verschiedenen Sprachen wenn gewünscht).
Folgendes Programm geht über alle Währungen und gibt die zentralen Informationen aus:
 

for ( Currency currency : Currency.getAvailableCurrencies() )
{
  System.out.printf( "%s, %s, %s (%s)%n",
                     currency.getCurrencyCode(),
                     currency.getSymbol(),
                     currency.getDisplayName(),
                     currency.getDisplayName(Locale.ENGLISH) );
}

Wir bekommen dann mehr als 200 Ausgaben, und die Ausgabe beginnt mit:
EGP, EGP, Ägyptisches Pfund (Egyptian Pound)
IQD, IQD, Irak Dinar (Iraqi Dinar)
GHS, GHS, Ghana Cedi (Ghana Cedi)
AFN, AFN, Afghani (Afghani)
MUR, MUR, Mauritius Rupie (Mauritius Rupee)
SGD, SGD, Singapur Dollar (Singapore Dollar)

Labels: ,

Wrapperklassen-Vergleiche durchführen mit compare() und compareTo()

Haben wie zwei Ganzzahlen 1 und 2 vor uns, so ist es trivial zu sagen, dass 1 kleiner 2 ist. Bei Fließkommazahlen ist das ein wenig komplizierter, da es hier „Sonderzahlen“ wie Unendlich oder eine negative bzw. positive null gibt. Da insbesondere Vergleichsalgorithmen die Beantwortung der Frage, ob zwei Werte a und b kleiner, größer oder gleich sind, erwarten, gibt es zwei Typen von Methoden in den Wrapper-Klassen.

·    Sie implementieren eine Objektmethode compareTo(). Die Methode ist nicht zufällig da, denn  Wrapper-Klassen implementieren die Schnittstelle Comparable. (Wir haben die Schnittstelle schon im Kapitel 6 kurz vorgestellt.)

·    Wrapper-Klassen besitzen statische compare()-Methoden.

Die Rückgabe der Methoden ist ein int und es kodiert, ob ein Wert größer, kleiner oder gleich ist.

Beispiel   Teste verschiedene Werte.

System.out.println( Integer.compare(1, 2) );        // -1
System.out.println( Integer.compare(1, 1) );        //  0
System.out.println( Integer.compare(2, 1) );        //  1

System.out.println( Double.compare(2.0, 2.1) );     // -1
System.out.println( Double.compare(Double.NaN, 0) );// 1

System.out.println( Boolean.compare(true, false) ); //  1
System.out.println( Boolean.compare(false, true) ); // -1

Ein true ist „größer“ als als false.   

Die Tabelle fasst von den Wrapper-Klassen die Methoden zusammen.

Klasse    Methode aus Comparable    Statische Methode compare()      
Byte    int compareTo(Byte anotherByte)    int compare(int x, int y)      
Short    int compareTo(Short anotherShort)    int compare(short x, short y)      
Float    int compareTo(Float anotherFloat)    int compare(float f1, float f2)      
Double    int compareTo(Double anotherDouble)    int compare(double d1, double d2)      
Integer    int compareTo(Integer anotherInteger)    int compare(int x, int y)      
Long    int compareTo(Long anotherLong)    int compare(long x, long y)      
Character    int compareTo(Character anotherCharacter)    int compare(char x, char y)      
Boolean    int compareTo(Boolean b)    int compare(boolean x, boolean y)   

Die Implementierung einer statischen Methode WrapperKlasse.compare() ist äquivalent zu WrapperKlasse.valueOf(x).compareTo(WrapperKlasse.valueOf(y)).

Die Klassen BigInteger, BigDecimal implementieren zwar Number und somit Comparable, aber eine statische compare()-Methode bieten sie nicht. Auch String implementiert Comparable, aber eine statische Methode fehlt. Der Grund ist, dass es eine statische Methode Objects.compare() gibt, zwei Objekte mit einem Comperator vergleicht.

Labels: ,

Die Utility-Klasse java.lang.Objects

In Java 7 ist die Klasse Objects hinzugekommen, die einige statische Utility-Funktionen bereithält. Sie führen in erster Linie null-Tests durch.

null-Tests um equals()/hashCode()/toString()

Ist zum Beispiel eine Objektvariable name einer Person null, so kann nicht einfach name.toString() aufgerufen werden, ohne dass eine NullPointerException folgt. Drei Methoden von Objects führen null-Test durch, bevor sie an die Object-Methode equals()/hashCode()/toString() weiterleiten. Eine zusätzliche Hilfsmethode arbeitet mit Comparatoren, die in im Kapitel über Datenstrukturen genauer vorgestellt werden.

class java.lang.Objects
§          static boolean equals( Object a, Object b )
Liefert true wenn beide Argument entweder null sind, oder a.equls(b) ebenfalls true ergibt. Sonst false. Das Objects.equals(null, null) die Rückgabe true ergibt ist sinnvoll und so erspart die Methode einige händische Tests.
§          static int hashCode( Object o )
Liefert 0 wenn o gleich null ist, sonst o.hashCode().
§          static String toString(Object o)
Liefert den String "null" wen das Argument null ist sonst o.toString().
§          static int compare( T a, T b, Comparator c )
Liefert 0, wenn a und b beide entweder null sind, oder der Comparator die Objekte a und b für gleich erklärt. Sind a und b beide ungleich null, so ist die Rückgabe c.compare(a, b). Ist nur a oder b gleich null, so hängt es vom Comparator ab und der Reihenfolge der Parameter ab.
Erinnern wir uns ans hashCode() vom Spieler, wo der Spielername in den Hashcode eingehen soll, so sehen wir, wo die statische Objects.hashCode()-Methode gut untergebracht werden kann.
com/tutego/insel/object/hashcode/Player.java, hashCode() Ausschnitt
result = 31 * result + ((name == null) ? 0 : name.hashCode());
Mit Objects.hashCode() verkürzt sich dies – nicht spektakulär im Sinne von eingesparten Zeichen – zu:
result = 31 * result + Objects.hashCode( name.hashCode() );

Null-Prüfungen mit eingebauter Ausnahmebehandlung

Zu den drei statischen Methoden kommen zwei hinzu, die null-Prüfungen übernehmen und im Fehlerfall eine Ausnahme auslösen. Das ist praktisch bei Konstruktoren oder Settern, die Werte initialisieren sollen, aber verhindern möchten, dass null durchgeleitet wird.

Beispiel   Die Methde setName() soll keine name-Argument gleich null erlauben.
public void setName( String name )
{
 this.name = Objects.nonNull( name );
}
Alternativ ist eine Fehlermeldung möglich:
public void setName( String name )
{
 this.name = Objects.nonNull( name, "name is not supposed to be null" );
}


class java.lang.Objects

§          static T nonNull( T obj )
Löst eine NullPointerExcpetion aus, wenn obj gleich null ist. Sonst lieferte obj als Rückgabe.
§          static T nonNull( T obj, String message )
Wie nonNull(obj), nur das die Meldung der NullPointerExcpeption bestimmt wird.

Labels: ,

Java 7 bringt binary literals und Underscores in literals

Im Build http://download.java.net/jdk7/changes/jdk7-b73.html gibt es noch mehr, aber diese beiden Änderungen betreffen die Sprache:

Labels:

Erste Sprachänderung in Java 7

Die Diamand-Schreibweise zur Abkürzung von generischen Instanziierungen ist in Java 7 Build 72 eingegangen.
  • http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6840638
  • http://download.java.net/jdk7/changes/jdk7-b72.html
  • http://download.java.net/jdk7/

Labels:

Die Sprachänderungen für Java 7 nun fest

Das schreibt Joseph D. Darcy in seinem Blog http://blogs.sun.com/darcy/entry/project_coin_final_five (und bei Java.net http://weblogs.java.net/blog/forax/archive/2009/08/29/seven-small-languages-changes-will-be-jdk7).
Die Spezifikationen stehen im Einzelnen noch nicht fest.

Raus sind erst einmal
und auch alle anderen Dinge.

Das alles erscheint mir schon mehr merkwürdig in der Auswahl. Improved Exception Handling for Java war so ein heißer Kandidat und wird es nun doch nicht.

Deadline ist Ende Oktober.

Labels:

Video mit Danny Coward über Java 7

Das Video gibt es bei Sun unter http://java.sun.com/developer/media/deepdivejdk7.jsp. Es zeigt die 5 Top-Features, die in Java 7 erwartet werden. Das Interview erwähnt noch das Swing Application Framework, was aber gestorben ist.

Labels:

Swing Application Framework fliegt aus Java 7 raus

Unter http://weblogs.java.net/blog/alexfromsun/archive/2009/08/saf_and_jdk7.html ist von Alexander Potochkin nun zu lesen:

After much discussion it's become clear that the Swing Application Framework API as it is today hasn't reached consensus and we feel still needs further design work done.

Since the SAF API was committed to milestone 5 of JDK7 and that time is already here, this date is now impossible, and we need to decommit SAF from any specific JDK 7 milestone

Labels: ,

NIO.2: Wahlfreier Zugriff mit SeekableByteChannel und ByteBuffer

Für den wahlfreien Zugriff auf Bytes in Dateien bietet Java seit Version 1.0 die Klasse RandomAccessFile. Unter Java 7 ist die Klasse nun nicht mehr nötig, und genaugenommen auch schon seit Java 1.4 nicht mehr, denn in Java 1.4 wurde das erste NIO-Paket eingeführt und mit ihm sogenannte Channel-Klassen, die eine offene Verbindung mit einem Datenkanal repräsentieren. Neu in Java 7 ist die Verbindung von Path und einem besonderen Channel für wahlfreie Ein-/Ausgabe, dem SeekableByteChannel, der ebenfalls in Java 7 eingeführt wurde.

SeekableByteChannel

Der SeekableByteChannel deklariert Operationen zum Lesen und Schreiben von Daten und zur Positionierung des Dateizeigers.

public interface java.nio.channels.SeekableByteChannel

extends ByteChannel

§ int read( ByteBuffer dst )

§ int write( ByteBuffer src )

§ long size()

§ long position()

§ SeekableByteChannel position( long newPosition )

§ SeekableByteChannel truncate( long size )

Die Methoden close() und isOpen() kommen aus Channel hinzu.

Es fällt auf, und das ist einer der großen Unterschiede zu RandomAccessFile, dass SeekableByteChannel kein byte-Feld oder einzelne Bytes liest oder schreibt, sondern einen ganz eigenen Typ, einen ByteBuffer, erwartet.

ByteBuffer

Ein ByteBuffer ist einem Byte-Feld sehr ähnlich; seine maximale Größe wird vorher festgelegt und kann später nicht dynamisch wachsen. Ist ein ByteBuffer angelegt, so können über einen Index die einzelnen Bytes gelesen und geschrieben werden, zum Beispiel mit byte get() oder put(byte b) relativ zur letzten Position, oder mit byte get(int index) und put(int index, byte b) absolut. Der wirkliche Unterschied ist aber, dass Java zwei verschiedene Arten von ByteBuffer-Implementierungen bietet (ByteBuffer ist eine abstrakte Klasse):

· Nicht-direkte ByteBuffer sind wie byte[]-Felder, also Java-Objekte, die auf dem Heap Platz einnehmen.

· Bei einem direkten ByteBuffer versucht Java einen Speicherbereich vom Betriebssystem zu bekommen. Während die nicht-direkten ByteBuffer und byte-Arrays auf dem Heap leben, und er normalen GC unterworfen sind, sollten die direkten ByteBuffer vom Betriebssystem verwaltet werden. Im Idealfall sind dadurch hohe Ein-/Ausgabegeschwindigkeiten möglich, denn mit direkten ByteBuffern kann sich das Betriebssystem Kopieroperationen zwischen nativen und Java-Puffern sparen.

Die Methoden auf direkten oder nicht-direkten ByteBuffern sind identisch. Insbesondere speichern die alle Puffer Zustände: Die Position, einen Limit und eine Kapazität. Diesen Eigenschaften wollen wir aber in der Insel nicht nachgehen.

Beispiel mit Path + SeekableByteChannel + ByteBuffer

Das folgende Beispiel fasst alles zusammen: Von einem Path wird über newByteChannel ein SeekableByteChannel erfragt. Anschließen leiten wir aus einer Zeichenkette über das byte[] einen nicht-direkten ByteBuffer ab und schreiben diesen in den SeekableByteChannel, sodass später die Datei Kurt Cobain.txt einen ASCII-Text enthält.

com/tutego/insel/nio2/SeekableByteChannelDemo.java, main()

Path p = Paths.get( "Kurt Cobain.txt" );

SeekableByteChannel raf = p.newByteChannel( StandardOpenOption.CREATE,

StandardOpenOption.WRITE );

String s = "Drugs are bad for you. ";

ByteBuffer byteBuffer = ByteBuffer.wrap( s.getBytes() );

raf.write( byteBuffer );

raf.write( ByteBuffer.wrap( "They will f*ck you up.".getBytes() ) );

raf.position( 34 );

raf.write( ByteBuffer.wrap( new byte[]{'u'} ) );

raf.close();

Das Beispiel zeigt, dass mit ByteBuffer.wrap() aus dem byte[] der Strings ein nicht-direkter Buffer angelegt wird, den write() dann in den Kanal schreibt.

kurt-in-hex

Nur zum Testen schreiben wie ASCII-Zeichen, was aber im „echten Leben“ eher nicht der Fall sein wird, denn wir müssen hier die korrekten Zeichenkodierungen beachten. Auch für sequenzielle Schreiboperationen ist der SeekableByteChannel eher weniger komfortabel – dennoch ist der Einsatz von Kanälen nicht per se falsch. Im nächsten Kapitel werden die Ströme vorgestellt, mit denen das Schreiben, insbesondere von Textdokumenten, viel einfacher wird.

FileChannel

Die Schnittstelle SeekableByteChannel gibt Operationen an, um die aktuelle Position auszulesen und den Positionszeiger neu zu setzen und über ByteBuffer Bytes und Bytefolgen zu lesen und zu schreiben. SeekableByteChannel ist dabei nicht an Dateien gebunden und enthält keine Informationen zu Dateipfaden oder sonstigen tiefer liegenden Schichten. Und da Path grundsätzlich ein Pfad auf alles Mögliche sein kann, etwa auf ein BLOB in der Datenbank, liefert newByteChannel() eine Rückgabe mindestens von Typ SeekableByteChannel, und damit erst ein mal keine Möglichkeiten dateispezifische Operationen vorzunehmen.

Wird allerdings newByteChannel() auf einem Pfad aufgerufen, der eine Datei vom Dateisystem repräsentiert, so ist die Rückgabe nicht einfach nur ein SeekableByteChannel, sondern der Untertyp FileChannel.[1] Ein Typcast ist daher möglich:

Path p = Paths.get( "Kurt Cobain.txt" );

FileChannel channel = (FileChannel) p.newByteChannel( options );

Da FileChannel die Schnittstelle SeekableByteChannel implementiert, bietet natürlich FileChannel alle Methoden zum Lesen, Schreiben und Positionieren. Zusätzlich bietet FileChannel aber Methoden, die explizit an Dateien gebunden sind. Drei Methoden fallen sofort auf:

· lock(): Sperrt die Datei (oder Dateiteile) für andere, soweit es das Betriebssystem unterstützt.

· force(): Updates werden sofort materialisiert, das heißt auf das Dateisystem übertragen.

· map(): Blendet die Datei, oder einen Teil der Datei, in den Speicher ein.

Die Methode map() ist besonders interessant. Damit kann ein FileChannel auf ein ByteBuffer abgebildet werden, sodass unsere Lese-/Schreiboperationen auf dem ByteBuffer direkt aus der Datei kommen oder direkt in die Datei gehen. Das Betriebssystem versucht sein bestes, die Operationen zu optimieren und geeignete Blöcke der Datei in den Speicher zu laden. Java und das Betriebssystem tuen damit ihr Bestes, die Operationen so schnell wie möglich und mit wenigen Kopieroperationen zwischen den internen Puffern vom Dateisystem und den Java-Puffern durchführen.

Das folgende Beispiel bezieht im ersten Schritt über newByteChannel() den FileChannel. Anschließend bildet die Methode map() die gesamte Datei auf einen MappedByteBuffer ab, der ein ByteBuffer ist, wie wir ihn im letzten Beispiel schon kennengelernt haben. Wir könnten nun Methoden auf dem ByteBuffer aufrufen, und die Bytes auslesen, doch hier gehen wir etwa anders vor: Die Bytes des ByteBuffer konvertiert ein CharsetDecoder von ASCII in Java-Unicode; das Ergebnis ist ein CharBuffer. Den CharBuffer laufen wir ab und geben die Zeichen auf der Konsole aus.

com/tutego/insel/nio2/FileChannelDemo.java, main()

Path p = Paths.get( "Kurt Cobain.txt" );

FileChannel fileChannel = (FileChannel) p.newByteChannel( StandardOpenOption.READ );

ByteBuffer byteBuffer = fileChannel.map( FileChannel.MapMode.READ_ONLY,

0, fileChannel.size() );

CharsetDecoder decoder = Charset.forName( "ASCII" ).newDecoder();

CharBuffer charBuffer = decoder.decode( byteBuffer );

while ( charBuffer.hasRemaining() )

System.out.print( charBuffer.get() );

Beim FileChannel gilt das gleiche wie beim SeekableByteChannel. Sequenzieller Lese- oder Schreibzugriff wird am Einfachsten über die Strom-Klassen realisiert. Sie werden im folgenden Kapitel vorgestellt.


[1] Den Typ FileChannel gibt es in Java schon länger, seit Java 1.4. Und vor Java 7 lieferten die Methoden getChannel() von FileInputStream, FileOutputStream und RandomAccessFile den FileChannel.

Labels: ,

OpenJDK7 / JDK7 M4 Release

Die Ankündigung wurde unter http://blogs.sun.com/xiomara/entry/openjdk7_jdk7_release_milestone_4 gemacht. Interessant ist "For now JDK 7 is finally in sync with the JDK 6u14 updates." Dann wird es Zeit, dass jetzt mal die *wirklichen* Dinge implementiert werden und man nicht nur die Änderungen von Java 6 alle in Java 7 nachzieht. Bis auf NIO.2 ist hier noch nicht wirklich viel passiert. Und beim Project Coin wird viel diskutiert, aber bisher ohne Ergebnis im Java-Compiler.

Labels:

Ausführlicher Artikel über invokedynamic

Den gibt es bei http://java.sun.com/developer/technicalArticles/DynTypeLang/index.html. Ed Od beschreibt ausführlich die einzelnen invokeXXX-Bytecode Operationen, und wie invokedynamic da hineinpasst.


Labels:

Vorschläge für Projekt Coin (Java 7 Sprachänderungen) geht in die nächste Runde

Eine Abstimmung hat ergeben, welche Spracherweiterungen es in Java 7 geben könnte:
In der näheren Auswahl sind desweiteren noch:
Alle andere Vorschläge, die bisher in der Mailingliste http://mail.openjdk.java.net/pipermail/coin-dev/ vorstellt wurden, sind damit raus. (Eine Begründung für die Ablehnung gibt es nicht zwangsläufig.)

Labels:

Nimbus Swing Look and Feel wird auch Teil von Java 7 sein

Von Java 6 wurde das Nimbus LaF auch auf das OpenJDK 7 gebracht. Es wird dann unter javax.swing.plaf liegen.

Nimbus Look and Feel Example

Labels:

Transparente und nicht-rechteckige Fenster

Kirill Grouchnikov gibt in seinem Blog-Eintrag http://www.pushing-pixels.org/?p=1209 den Hinweis, dass in JDK 7 (b57) transparente und nicht-rechteckige Fenster unterstützt werden. Die Sun-interne Klasse com.sun.awt.AWTUtilities wird nun vom Entwickler nicht mehr benötigt, und die Funktionalität ist in die Windows-Klasse gewandert; sie delegiert aber weiterhin an AWTUtilities.

Labels:

Größe vom Integer.valueOf Cache ist nun konfigurierbar

Unter http://hg.openjdk.java.net/jdk7/jdk7/jdk/rev/4c3f752993a5 ist die Änderung sichtbar, mit der in Java 7 der Cache für die Integer-Objekte nun nicht mehr zwangsläufig im Bereich –128 bis +127 liegen muss. Ändern kann man die Cache-Größe auf der Kommandozeile mit -XX:AutoBoxCacheMax=<size>.

Labels:

JSR 203 (NIO2) im OpenJDK 7

Damit hat es die erste Bibliothek in Java 7 (Build 53: http://download.java.net/jdk7/) geschafft. Die neue File-API (http://download.java.net/jdk7/docs/api/java/nio/file/package-summary.html) ist sehr leistungsfähig und auch schon relativ gut dokumentiert:

Labels:

Na endlich: JDK 7 feature list, build and integration schedule

Mark Reinhold schreibt auf seinem Blog:

At Devoxx back in December I presented a list of candidate features for JDK 7(video, interview); more recently, at FOSDEM, I discussed that list as well as the high-level schedule and the means by which we plan to deliver the release (slides, audio/video). The high-level schedule is now available on the JDK 7 Project page in theOpenJDK Community along with a detailed feature list, the near-term build and integration schedule, and the long-term, build-by-build calendar. The JDK 7 feature list, like that of any large software project, is provisional and subject to change based upon new information and new proposals. Who Many of the planned features will be implemented by Sun engineers; the rest will be contributed from outside Sun. We’ll shortly define a process by which additional features can be proposed—so long as you’re willing to write not just the code but also the necessary tests and specification material. There will also be a simpler, lighter-weight process for smaller changes. Where Regardless of who writes the code, the expectation is that all development will take place in the open, either in the JDK 7 Project, in some other OpenJDK Project, or elsewhere. What about the JCP? The JDK 7 Project is creating a prototype of what might—or might not—wind up in the Java SE 7 Platform Specification. When the SE 7 Platform JSR is submitted then the features under development in JDK 7 will be proposed for inclusion therein, except for those that are VM-level or implementation-specific.

Der Kalender gibt Ende Februar an.

M8 2010/02/12 – 2010/02/18 b95

Die Feature-List ist interessant aber ich denke, dass einiges im Detail noch offen ist:

  • JSR TBD: Small language enhancements (Project Coin)
  • JSR 296: Swing application framework
  • Swing updates

Labels:

Java 7 und Named Capturing Group

Hatte man bei reguläre Ausdrücken und Gruppen auf die Gruppen über eine Index zugreifen müssen, so kann man dies in Java 7 auch über einen Namen tun; das ganze nennt sich dann named capturing group:

  String pStr = "0x(?<bytes>\\p{XDigit}{1,4})\\s++u\\+(?<char>\\p{XDigit}{4})(?:\\s++)?";
    Matcher m = Pattern.compile(pStr).matcher(INPUTTEXT);
    if (m.matches()) {
        int bs = Integer.valueOf(m.group("bytes"), 16);
        int c =  Integer.valueOf(m.group("char"), 16);
        System.out.printf("[%x] -> [%04x]%n", bs, c);
    }

Der Blog-Eintrag http://blogs.sun.com/xuemingshen/entry/named_capturing_group_in_jdk7 gibt es in paar mehr Infos.

Labels:

URLClassLoader schließen in Java 7

URLClassLoader bekommt in Java 7 eine close()-Methode: http://blogs.sun.com/CoreJavaTechTips/entry/closing_a_urlclassloader.

Labels:

Video: Java 7, Modularisierung, Jigsaw, was kommt, was geht, 2010

Mark Reinhold spricht über Java 7

 

Geplante Änderungen:

Wird (wohl) nicht in Java 7 kommen wird:

  • Closures
  • Reified Generics
  • First Class Properties
  • Überladene Operatoren
  • BigDecimal Syntax
  • JSR 295: Beans Binding

Der Hammer: Java 7 wird Anfang  2010 erwartet.

Wer mal was inspirierendes hören möchte: Unter http://channel9.msdn.com/posts/Charles/C-40-Meet-the-Design-Team/ sprechen C#-Macher Anders Hejlsberg und weitere C#-Experten über die Zukunft von C# und .NET, insbesondere im Kontext dynamischer Sprachen. Hier läuft man meilenweit vor Java; und das Interview ist schon 1 Jahr alt... Unter http://code.msdn.microsoft.com/csharpfuture geht's es dann mit ein paar Links zur C# 4 und Ideen zu C# 5 weiter.

Labels:

Auswirkung von neuen Sprachkonstrukturen in Java 7 und Project Coin

Joseph Darcy veröffentlichte schon vor 2 Jahren einen interessanten Blog-Artikel über die Änderungen, die enum mit sich brachte: JSL, Compiler, Bibliotheken, JVM-Spezifikation, usw. Da Sun sich mit dem neuen Modulsystem Jigsaw wohl ganz gut was vorgenommen hat, wird es große Änderungen an der Sprache (wie Closures oder Reified Generics) wohl nicht in Java 7 geben. Dennoch gibt es mit dem aktuellen Project Coin: Small Language Changes for JDK 7 einen neuen Versuch, zumindest einige Features unterzubringen, die mit wenig Aufwand implementiert werden können. Dazu zählen ein switch mit Strings oder die Möglichkeit, multiple Exceptions in einem catch zu fangen.

Labels:

Joda Time 1.6/JSR 310

Von Joda Time (http://joda-time.sourceforge.net/) gibt es ein neues Update in der Version 1.6 (vom 2008-10-27). Interessant bleibt zu sehen, wie sich Joda Time im Laufe der Zeit gegenüber der JSR 310: A New Java Date/Time API abhebt.

Zur JSR 310, die in Java 7 erwartet wird:

Stephen Colebourne ist einer der treibenden Personen von Joda Time und auch Specification Lead der JSR 310.

Labels: ,

Erster Java Closures Prototype im openjdk

Von http://gafter.blogspot.com/2008/08/java-closures-prototype-feature.html:

The complete source code, released under GPLv2, is in the project's openjdk repository. A binary build, suitable for use with an existing JDK6, is at http://www.javac.info/closures.tar.gz. Other related documents are on the website http://www.javac.info/

Neu sind Method references aus dem  FCM proposal:

{ String => int } parseInt = Integer#parseInt(String);
int x = parseInt.invoke("42");


Damit wird es immer wahrscheinlicher, dass es Closures in Java 7 gibt, obwohl Zeitpunkt und Inhalte immer noch nicht feststehen.

Labels:

BGGA Closures werden wohl das Rennen in Java 7 machen

Beim http://openjdk.java.net/ Projekt ist ein Unterprojekt http://openjdk.java.net/projects/closures/ eingerichtet worden:

This goal of this Project is to produce a feature-complete prototype of the Java bytecode compiler (javac) for the draft BGGA Closures specification. This Project is sponsored by the OpenJDK Compiler Group.

Wenn es also Closures in Java schaffen -- einige Stimmen sagen, dass es dafür noch zu früh ist -- dann wohl diese Syntax. Da das Projekt sehr neu ist, befinden sich in der Mailing-Liste noch nicht viele Nachrichten.

Derweil hat sich die Closures-Spezifikation (Homepage http://www.javac.info/) von der Version 0.5 nicht weiter bewegt und auch die Open Issues sind nicht geklärt.

Interessant ist auch die http://www.javac.info/google-position.html (Neal Gafter ist Angestellter bei Google)

As of April 7, 2008, the following is Google's position on proposals to add support for closures to Java: Google believes Java platform will likely benefit from continued research into closures. To arrive at the best solution, Google is open to multiple parallel investigations but is not currently prepared to commit to any particular proposal. We do not expect these investigations to yield results in time for Java 7, and are of the opinion that it is premature to launch a JSR that forces us down any specific path.

Labels:

Java Closures : Inselupdate für Java 7

Mit den Closures nach Gilad Bracha, Neal Gafter, James Gosling, Peter von der Ahé.

Innere Klassen, insbesondere anonyme innere Klassen, sind bisher die einzige Möglichkeit, um Programmteile an Methoden zu übergeben. Nehmen wir einen Timer als Beispiel. Dem eigentlichen Zeitgeber muss ein Stücken Code übergeben werden, sodass der Timer weiß, was er zu tun hat. Mit dem Zeitgeber, der Klasse Timer, und der abstrakten Basisklasse TimerTask zur Beschreibung der Aufgaben, ist schnell ein Beispiel programmiert, das wie eine Uhr in jeder Sekunde die Zeit auf dem Bildschirm ausgibt:


public class TimerExample
{
public static void main( String[] args )
{
class MyTimer extends java.util.TimerTask {
@Override public void run() {
System.out.println( new java.util.Date() );
}
}

new java.util.Timer().scheduleAtFixedRate( new MyTimer(), 0, 1000 );
}
}

Für die Beschreibung des Programmcodes ist extra eine eigene Klasse erforderlich. Über eine innere anonyme Klasse lässt sich der Programmcode jedoch noch etwas weiter verkürzen:

 public class TimerExample
{
public static void main( String[] args )
{
new java.util.Timer().scheduleAtFixedRate( new java.util.TimerTask() {
@Override public void run() {
System.out.println( new java.util.Date() );
}
}, 0, 1000 );
}
}

Aus dem Programm ist deutlich abzulesen, dass zum "Transport" der println()-Anweisung einiges an Schreibarbeit nötig ist. Wünschenswert ist es aber, wenn der Programmcode leichter an die Funktion scheduleAtFixedRate() zu übergeben wäre. Da die bereitgestellte Funktion bisher nicht so programmiert ist, ist das Ziel, dieses nachzuprogrammieren.

Deklaration eines Closures

Ein Closure repräsentiert einen Block Java-Code. Die allgemeine Schreibweise ist

{ formal parameters => statements expression }

Die Schreibweise definiert eine anonyme Funktion. Vergleichen wir einer bekannten Funktionsdeklaration

void out()
{
System.out.printn("Hallo Welt");
}

mit der Deklaration eines Closures:

{ => System.out.printn("Hallo Welt"); }

Während normale Funktionen mit ihrem Namen aufgerufen werden, steht eine Closure für ein Objekt, welches eine invoke()-Methode anbietet.

public class Closures
{
public static void main( String[] args )
{
{ => System.out.println("Hallo Welt"); }.invoke();
}
}

Aus der allgemeinen Schreibweise

{ formal parameters => statements expression }

lässt sich absehen, dass formale Parameter wie bei normalen Funktionsdeklarationen möglich sind. So lassen sich in den Closure-Block Daten einführen. Vergleichen wir wieder eine Funktions- mit einer Closure-Deklaration:

void quote( String s )
{
System.out.println("'" + s + "'");
}

Der Closure mit Beispiel:

public class Closures
{
public static void main( String[] args )
{
{ String s => System.out.println("'" + s + "'"); }.invoke( "tutego" );
}
}

Zwei Dinge fallen an dem Beispiel auf:
• Formale Parameter haben einen Typ (links vom Pfeil steht String s ).
• Die invoke()-Funktion nimmt immer so viele Argumente an, wie es formale Parameter in der Closure-Deklaration gibt.

Die Beispiele bisher zeigen Closures, die in ihrem Rumpf eine Anweisung tragen. Closures können aber auch Ausdrücke enthalten.

public class Closures
{
public static void main( String[] args )
{
System.out.println( { int a, int b => (a + b) / 2 }.invoke( 10, 20) ); // 15
}
}

Im Rumpf endet die Ausdruck nicht mit einem Semikolon, denn es wäre ja auch verboten,

System.out.println( (a + b) / 2; );

zu schreiben.

Ein auffälliger Unterschied zur Funktion ist die fehlende return-Anweisung. Closures selbst sind quasi an die Aufrufstelle eingesetzte Programmteile und ein return würde die Funktion verlassen!

Funktions-Typ

Closures, wie { int a, int b => (a + b) / 2 }, besitzen einen so genannten Funktions-Typ, der durch die Typen der Parameter und der Rückgabe bestimmt ist. Die allgemeine Notation ist

{ formal parameters => return type }

Gibt eine Funktion nicht zurück, so steht rechts vom Pfeil void. Für unsere drei bisherigen Closures sind die Typen:

{ => void }
{ String => void }
{ int, int => int }

Da in einer Funktionsdeklaration ohne Parameter ja auch kein void steht – void out(void) ist falsch – steht auch im ersten Fall links vom Pfeil nichts.
Mit diesem Funktions-Typ lassen sich die Closures ausgezeichnet referenzieren:

{ => void } printHelloWorld = { => System.out.println("Hallo Welt"); }; 

{ String => void } printQuoted = { String s => System.out.println("'" + s + "'"); };

{ int, int => int } avg = { int a, int b => (a + b) / 2 };

printHelloWorld.invoke(); // Hallo Welt

printQuoted.invoke( "tutego" ); // 'tutego'

System.out.println( avg.invoke( 10, 20 ) ); // 15

Mit diesem Wissen lassen sich Funktionen schreiben, die ein Closure entgegennehmen. Eine Funktion repeat() soll dabei einen Programmcode so oft wie gewünscht aufrufen:

public class Repeater
{
public static void repeat( int times, { => void } block )
{
for ( int i = 0; i < times; i++ )
block.invoke();
}

public static void main( String[] args )
{
repeat( 2, { => System.out.println("Hallo"); } );
}
}

Das Hallo kommt also zweimal auf den Bildschirm.

In der Java-Bibliothek sind nicht alle Funktionen so parametrisiert, so dass Closures als Parameter erlaubt sind. Schreiben wir für den Timer eine eigene Methode scheduleAtFixedRate(), die einen Codeblock entgegennimmt und ausführt:

public class TimerExample
{
public static void scheduleAtFixedRate( { => void } task, long delay, long period )
{
new java.util.Timer().scheduleAtFixedRate( new java.util.TimerTask() {
@Override public void run() {
task.invoke();
}
}, delay, period );
}
public static void main( String[] args )
{
scheduleAtFixedRate(
{ => System.out.println( new java.util.Date() ); }
, 0, 1000 );
}
}

Mit Closures sind auch Funktionszeiger leicht zu realisieren:

public class FunctionPointerWithClosures
{
static void invoker( { => void } block )
{
block.invoke();
}

public static void main( String[] args )
{
{ => void } method1 = { => System.out.println("Hello www.tutego.com"); };
{ => void } method2 = { => System.out.println("Hallo www.tutego.com"); };

invoker( Math.random() > 0.5 ? method1 : method2 );
}
}

Natürlich lassen sich jetzt die Methoden auch in eine Datenstruktur setzen:

Map<String, { => void }> methods = new HashMap<String, { => void }>();
methods.put( "German", { => System.out.println("Hallo www.tutego.com"); } );
methods.put( "English", { => System.out.println("Hello www.tutego.com"); } );

methods.get( "German" ).invoke();

for ( { => void } method : methods.values() )
invoker( method );

In ein Feld können Closures nicht gesetzt werden! Der Grund liegt an der internen Generics-Umsetzung.

Nehmen wir an, wir wollen eine Funktion each() schreiben, die einen String mit einem gegebenen Delimiter zerlegt und dann eine Operation auf den einzelnen Token ausführt. Soll weiterhin die Operation einen String zurückgeben, so kann das in herkömmlichem Java etwa so implementiert werden:

class StringUtils
{
public static interface StringInStringOutBlock
{
String execute( String in );
}

public static String each( String source, String delimiter, StringInStringOutBlock block )
{
StringBuilder result = new StringBuilder();

for ( String token : source.split( delimiter ) )
result.append( block.execute( token ) );

return result.toString();
}
public static void main( String[] args )
{
class QuoterBlock implements StringInStringOutBlock {
@Override public String execute( String in ) {
return "'" + in + "'";
}
}

String s = each( "Hallo Welt!", " ", new QuoterBlock() );
System.out.println( s );
}
}

Closures machen das viel kompakter:

class StringUtils
{
public static String each( String source, String delimiter, { String => String } block )
{
StringBuilder result = new StringBuilder();

for ( String token : source.split( delimiter ) )
result.append( block.invoke( token ) );

return result.toString();
}

public static void main( String[] args )
{
String s = each( "Hallo Welt!", " ", { String in => "'" + in + "'" } );
System.out.println( s );
}
}

Closure mit Variablenzugriff

Closures haben einige Eigenschaften, die innere anonyme Klassen so erst einmal nicht haben. Eine davon ist, dass ein Closure-Block auf auch nicht-finale Werte lesend und schreiben zugreifen kann.

int i = 0;
repeat( 3, { => System.out.println(i); } ); // 0 0 0

@Shared int j = 0;
repeat( 3, { => System.out.println(j++); } ); // 0 1 2
System.out.println( j ); // 3

Auffällig ist die Annotation @Shared. Annotiert sie nicht die Variablen, auf die der Closure schreibend zugreift, gibt es eine Compiler-Warnung (kein Fehler!): "warning: [shared] captured variable j not annotated @Shared".

Closure Conversion

Damit automatisch Java-Entwicklung ohne Anpassung der Bibliotheken von den Closures profitieren, haben die Sprachentwickler einen speziellen Mechanismus eingebaut: Ein Closure kann einem Interface mit einer Operation zugewiesen werden, wenn die Rückgabe- und Parametertypen übereinstimmen. Die Schnittstelle Runnable und ActionListener sind wie folgt deklariert:

public interface Runnable {
void run();
}
public interface ActionListener extends EventListener {
void actionPerformed(ActionEvent e);
}

Kompatible Closures sind:

Runnable run = { => System.out.println("Nebenläufig!"); };
ActionListener listener = { ActionEvent l => System.out.println("Gedrückt!"); };


Ohne Zuweisung an Variablen ist ein nebenläufiges Programm schnell gestartet und ein Ereignisbehandler ohne viel Programmcode an einer Schaltfläche festgemacht:



new Thread( { => System.out.println("Nebenläufig!"); } ).start();
JButton b = new JButton();
b.addActionListener( { ActionEvent l => System.out.println("Gedrückt!"); } );

Diese automatische Konvertierung ist wirklich sehr praktisch, denn viele wichtige Java-Schnittstellen schreiben nur eine Operation vor.

Nach einem Vormittag mit Java-Closures kann ich sagen, dass ich gut mit der Syntax leben kann. Ich mag's! Schauen wir mal, ob sich noch groß etwas ändert.

Labels: ,

Closures - Muss das wirklich sein?

Nach dem vierten Proposal (http://www.javac.info/closures-v04.html) für Closures von Gilad Bracha, Neal Gafter (der mit dem Java-Puzzlers), James Gosling und Peter von der Ahé (nach seiner Whishlist unter http://blogs.sun.com/ahe/entry/java_se_7_wish_list werden weitere abgefahrene Sachen möglich sein) wird in der Community heftig diskutiert, ob Closures nötig sind. Ich finde sie toll! Zwar sehe ich schon meine Teilnehmer über den Ausdrücken brüten und zweifeln an der Aussage "Java ist eine einfache objektorientierte Programmiersprache" zweifeln, doch denke ich, dass es unausweichlich ist, Java mehr sprachliche Möglichkeiten zu bieten.

Java: A simple, object-oriented, network-savvy, interpreted, robust, secure, architecture neutral, portable, high-performance, multithreaded, dynamic language [...] Java omits many rarely used, poorly understood, confusing features of C++ that in our experience bring more grief than benefit. [http://java.sun.com/docs/overviews/java/java-overview-1.html]

Dass Java im Moment so viele Veränderungen erfährt ist vielleicht auch eine Frage der Wahrnehmung. Sun war mit Grammatikänderungen bisher sehr konservativ und seit mehr als 10 Jahren haben wir bis auf den Wechsel von Java 1.0 auf Java 1.1 und Java 1.4 auf Java 5.0 kaum Änderungen gesehen; strictfp/Sprachänderungen wäre man so ein Außenseiter. Nun gibt es stärkere Veränderungen, wie wir sie bei anderen Sprachen auch erleben. C# hat sich in den letzten Jahren unglaublich verändert --- was als Java-Klon begann, ist heute eigenständig und als Sprache hoch innovativ. Mitunter starke strukturelle Änderungen lassen sich auch bei Skriptsprachen ausmachen, etwa Python (trinärer Operator, try/except/finally), PHP (die ganzen OOP-Eigenschaften), Perl (für Perl 2.6 ebenfalls Änderungen der OOP-Schreibweise/perl6-language-objects und funktionale Programmierelemente geplant) aber auch C++ und SQL. Als Java-Entwickler hat uns Sun nur in den letzten Jahren so "erzogen" dass es kaum große Änderungen gab, was sich nun ändert. Hier muss Sun uns also wieder "umerziehen".

Dass Java keine einfache Programmiersprache ist, müsste jedem eigentlich bewusst geworden sein, der didaktisch versucht, Konzepte wie Generics zu vermitteln. Unternehmen müssen auch mit nicht-Cracks Software entwickeln können und können nicht darauf bauen, dass alle Mitarbeiter Spezifikationen lesen und Best-Practices befolgen. (IMHO kann man mit OOP-Spezialisten in jeder Programmiersprache exzellente Ergebnisse erziehlen.) Eine Sprache muss daher auch immer ein bisschen Narrensicher sein. (Ein kleiner Seitenhieb auf Perl, der Sprache, der man "Write Once Read Never" (WORN) nachsagt.) Da sehe ich in Closures auf jeden Fall ein Problem, denn während Generics doch noch einfach zu lesen sind (von der Deklaration sprechen wir nicht!) sieht das bei Closure-Nutzungen etwas anders aus. Hier muss der Java-Entwickler wieder eine neue Syntax lernen.

Bisher ist in der neuen Syntax kein Wort von neuen Schlüsselwörtern. Das macht die Anwendung einfacher als es bei Java 5 mit enum war. Ein weiterer Punkt ist, dass man über allgemeine Closures Dinge schreiben kann, die wie spezielle Lösungen aussehen. Ein häufig zitiertes Beispiel ist der Lock:

withLock( lock ) {
    System.out.println( "Closures in Java 7" );
}
 

Das ist eine Kurzform für

 
withLock( lock, {=>
    System.out.println( "Closures in Java 7" );
} );
 

Es ist gerade nicht withLock() ein spezielles Java-Konstrukt, sondern nur die Anwendung eines allgemeinen Closures. Intern wird das Umgesetzt über übliche Schnittstellen. Und mit { => } sieht man schon, wie Closures in Java aussehen können.

In der Summe finde ich Closures toll und freue mich schon. Zwar habe ich schon die ersten Ideen, wie man das didaktisch umsetzen kann, aber mal sehen, was bis dahin geschrieben wird. Was Madbean unter http://madbean.com/2006/closure/ zeigt, finde ich schon nett. Doch bis Java 7 "freigelassen" wird, dauert es noch. Wer sich schon einmal vorbereiten möchte, der sollte sich zum Beispiel die Präsentation von Neal Gafter unter http://www.bejug.org/confluenceBeJUG/display/PARLEYS/Closures+for+Java anschauen.

 

Christian Ullenboom

PS: Wir haben das Seminar "Dynamische Webseiten mit JavaScript und DOM" [http://www.java-tutor.com/seminare/skriptsprachen-schulung/javascript-schulung.html] aktualisiert.

Labels: