RichTextFX (GPLv2 with the Classpath Exception)

Seit JavaFX 8 kann JavaFX dank TextFlow problemlos Text mit unterschiedlichen Formatierungen anzeigen. RichTextFX (ehemals CodeAreaFX) greift darauf zurück und bietet mit StyleClassedTextArea eine Komponente, bei der einfach gewisse Teile vom Text ausgezeichnet werden können. Ein kleiner Editor kommt als Demo mit:

Screenshot of the RichText demo

Weiterhin gibt es eine vorgefertigte Editor-Komponente an, die als Basis für eigene Code-Editoren dienen kann:

Screenshot of the JavaKeywords demo

Über Code oder CSS lässt sich die Darstellung ändern. 

Weiter FX-Komponenten unter http://www.tutego.de/java/javafx-komponenten.htm.

ReactFX (https://github.com/TomasMikula/ReactFX)

Thomas Mikula fasst die JavaFX 8 Bibliothek ReactFX mit den Worten "Reactive event streams, inhibitable bindings and more for JavaFX" zusammen. Die Bibliothek veröffentlicht spezifische Ereignisse einer JavaFX-Anwendung auf einer Art lokalen Bus (Typ EventStream) und erlaubt es auf der anderen Seite Klienten an diesem EventStream Bus zu lauschen. Geht es zum Beispiel darum Maus-Klicks auf einem Knoten zu registrieren und dann bei Klicks Code auszuführen sieht es im Code so aus:

EventStream<MouseEvent> clicks = EventStreams.eventsOf( node, MouseEvent.MOUSE_CLICKED );
clicks.subscribe( click -> System.out.println("Klick!") );

So gesehen bietet die API noch keinen Vorteil, spannend wird es, wenn der EventStream gefiltert, mit anderen EventStreams verschmolzen oder die Ereignisse gemappt werden — die Programmierung erinnert an die neue Stream-API aus Java 8.

Weitere FX-Komponenten unter http://www.tutego.de/java/javafx-komponenten.htm.

tutego bietet auch ein JavaFX-Seminar an: http://www.tutego.de/seminare/java-schulung/JavaFX-Seminar-JavaFX-Script-Kurs.html

Funktionale Schnittstelle in Java 8 aus java.util.function

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

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

Beispiele einiger vordefinierter funktionaler Schnittstellen

Weiterlesen

jdeps Kommandozeilentool in Java 8

Das JDK bringt mit jdeps ein kleines statisches Analysewerkzeug mit, welches die statischen Abhängigkeiten eines Java-Programms aufzeigt. Dabei listet es alle referenzierten Pakete auf und optional noch die Profile.

 

$ jdeps

Usage: jdeps <options> <classes…>

where <classes> can be a pathname to a .class file, a directory, a JAR file,

or a fully-qualified class name. Possible options include:

-dotoutput <dir> Destination directory for DOT file output

-s -summary Print dependency summary only

-v -verbose Print all class level dependencies

-verbose:package Print package-level dependencies excluding

dependencies within the same archive

-verbose:class Print class-level dependencies excluding

dependencies within the same archive

-cp <path> -classpath <path> Specify where to find class files

-p <pkgname> -package <pkgname> Finds dependences in the given package

(may be given multiple times)

-e <regex> -regex <regex> Finds dependences in packages matching pattern

(-p and -e are exclusive)

-include <regex> Restrict analysis to classes matching pattern

This option filters the list of classes to

be analyzed. It can be used together with

-p and -e which apply pattern to the dependences

-P -profile Show profile or the file containing a package

-apionly Restrict analysis to APIs i.e. dependences

from the signature of public and protected

members of public classes including field

type, method parameter types, returned type,

checked exception types etc

-R -recursive Recursively traverse all dependencies

-jdkinternals Finds class-level dependences on JDK internal APIs.

By default, it analyzes all classes on -classpath

and input files unless -include option is specified.

This option cannot be used with -p, -e and -s options.

WARNING: JDK internal APIs may not be accessible in

the next release.

-version Version information

 

Ein Beispiel:

 

$ jdeps "c:\Program Files\Java\jdk1.8.0\lib\ant-javafx.jar"

c:\Program Files\Java\jdk1.8.0\lib\ant-javafx.jar -> c:\Program Files\Java\jdk1.8.0\jre\lib\rt.jar

c:\Program Files\Java\jdk1.8.0\lib\ant-javafx.jar -> not found

com.javafx.main (ant-javafx.jar)

-> java.applet

-> java.awt

-> java.awt.event

-> java.io

-> java.lang

-> java.lang.reflect

-> java.net

-> java.security

-> java.util

-> java.util.jar

-> javax.swing

-> sun.misc JDK internal API (rt.jar)

com.sun.javafx.tools.ant (ant-javafx.jar)

-> java.io

-> java.lang

-> java.security.cert

-> java.util

-> java.util.jar

-> java.util.zip

-> org.apache.tools.ant not found

-> org.apache.tools.ant.taskdefs not found

-> org.apache.tools.ant.types not found

-> org.apache.tools.ant.types.resources not found

-> sun.misc JDK internal API (rt.jar)

com.sun.javafx.tools.packager (ant-javafx.jar)

-> java.io

-> java.lang

-> java.lang.reflect

-> java.math

-> java.net

-> java.nio.file

-> java.security

-> java.security.cert

-> java.text

-> java.util

-> java.util.jar

-> java.util.regex

-> java.util.zip

-> sun.misc JDK internal API (rt.jar)

-> sun.security.pkcs JDK internal API (rt.jar)

-> sun.security.timestamp JDK internal API (rt.jar)

-> sun.security.util JDK internal API (rt.jar)

-> sun.security.x509 JDK internal API (rt.jar)

com.sun.javafx.tools.packager.bundlers (ant-javafx.jar)

-> java.io

Funktionale Programmierung mit Java

Programmierparadigmen: imperativ oder deklarativ

In irgendeiner Weise muss ein Entwickler sein Problem in Programmform beschreiben, damit der Computer es letztendlich ausführen kann. Hier gibt es verschiedene Beschreibungsformen, die wir Programmierparadigmen nennen. Bisher haben wir uns immer mit der imperativen Programmierung beschäftigt, bei der Anweisungen im  Mittelpunkt stehen. Wir haben im Deutschen den Imperativ, also die Befehlsform, die sehr gut mit dem Programmierstil vergleichbar ist, denn es handelt sich in beiden Fällen um Anweisungen der Art „tue dies, tue das“. Diese „Befehle“ mit Variablen, Fallunterscheidungen, Sprüngen beschreiben das Programm und den Lösungsweg.

Zwar ist imperative Programmierung die technisch älteste, aber nicht die einzige Form Programme zu beschreiben; es gibt daneben die deklarative Programmierung, die nicht das „wie“ zur Problemlösung beschreibt, sondern das „was“, also was eigentlich gefordert ist ohne sich in genauen Abläufen zu verstricken. Auf den ersten Blick klingt das abstrakt, aber für jeden, der schon einmal

  • einen Selektion wie *.html auf der Kommandozeile/im Explorer-Suchefeld getätigt,
  • eine Datenbankanfrage mit SQL geschrieben,
  • eine XML-Selektion mit XQuery genutzt,
  • ein Build-Skript mit Ant oder make formuliert,
  • eine XML-Transformation mit XSLT beschrieben hat

wird das Prinzip kennen.

Bleiben wir kurz bei SQL, um einen Punkt deutlich zu machen. Natürlich ist im Endeffekt die Abarbeitung der Tabellen und Auswertungen der Ergebnisse von der CPU rein imperativ, doch es geht um die Programmbeschreibung auf einem höheren Abstraktionsniveau. Deklarative Programme sind üblicherweise wesentlicher kürzer und damit kommen weitere Vorteile wie leichtere Erweiterbarkeit, Verständlichkeit ins Spiel. Da deklarative Programme oftmals einen mathematischen Hintergrund haben, lassen sich die Beschreibungen leichter formal in ihrer Korrektheit beweisen.

Deklarative Programmierung ist ein Programmierstil, und eine deklarative Beschreibung braucht eine Art „Ablaufumgebung“, denn SQL kann zum Beispiel keine CPU direkt ausführen. Aber statt nur spezielle Anwendungsfälle wie Datenbank- oder XML-Abfragen zu behandeln, können auch typische Algorithmen deklarativ formuliert werden, und zwar mit funktionaler Programmierung. Damit sind imperative Programme und funktionale Programme gleich mächtig in ihren Möglichkeiten.

Funktionale Programmierung und funktionale Programmiersprachen

Bei der funktionalen Programmierung stehen Funktionen im Mittelpunkt und ein im Idealfall zustandsloses Verhalten, in dem viel mit Rekursion gearbeitet wird. Ein typisches Beispiel ist die Berechung der Fakultät. Es ist n! = 1 · 2 · 3 · … · n, und mit Schleifen und Variablen, dem imperativen Weg, sieht sie so aus:

public static int factorial( int n ) {
  int result = 1;
  for ( int i = 1; i <= n; i++ )
    result *= i;
  return result;
}

Deutlich sind die vielen Zuweisungen und die Fallunterscheidung durch die Schleife abzulesen; die typischen Indikatoren für imperative Programme. Der Schleifenzähler erhöht sich, damit kommt Zustand in das Programm, denn der aktuelle Index muss ja irgendwo im Speicher gehalten werden. Bei der rekursiven Variante ist das ganz anders, hier gibt es keine Zuweisungen im Programm und die Schreibweise erinnert an die mathematische Definition:

public static int factorial( int n ) {
  return n == 0 ? 1 : n * factorial( n - 1 );
}

Mit der funktionalen Programmierung haben wir eine echte Alternative zur imperativen Programmierung. Die Frage ist nur: Mit welcher Programmiersprache lassen sich funktionale Programme schreiben? Im Grunde mit jeder höheren Programmiersprache! Denn funktional zu programmieren ist ja ein Programmierstil, und Java unterstützt funktionale Programmierung, wie wir am Beispiel mit der Fakultät ablesen können. Da das im Prinzip schon alles ist, stellt sich die Frage, warum funktionale Programmierung einen so schweren Stand hat und bei den Entwicklern gefürchtet ist. Das hat mehrere Gründe:

Lesbarkeit. Am Anfang der funktionalen Programmiersprachen steht historisch LISP aus dem Jahr 1958, eine sehr flexible, aber ungewohnt zu lesende Programmiersprache. Unsere Fakultät sieht in LISP so aus:

(defun factorial (n) (if (= n 1) 1 (* n (factorial (- n 1)))))

Die ganzen Klammern machen die Programme nicht einfach lesbar und die Ausdrücke stehen in der Präfix-Notation – n 1 statt der üblichen Infix-Notation n – 1. Bei anderen funktionalen Programmiersprachen ist es anders, dennoch führt das zu einem gewissen Vorurteil, dass alle funktionalen Programmiersprachen schlecht lesbar sind.

Performance und Speicherverbrauch. Ohne clevere Optimierungen von Seiten des Compilers und der Laufzeitumgebung führen insbesondere rekursive Aufrufe zu prall gefüllten Stacks und schlechter Laufzeit.

Rein funktional. Es gibt funktionale Programmiersprachen, die als „rein“ oder „pur“ bezeichnet werden und keine Zustandsänderungen erlauben. Die Entwicklung von Ein-/Ausgabeoperationen oder simplen Zufallszahlen ist ein großer Akt, der für normale Entwickler nicht mehr nachvollziehbar ist. Die Konzepte sind kompliziert, doch zum Glück sind die meisten funktionalen Sprachen nicht so rein und erlauben Zustandsänderungen, nur Programmierer versuchen genau diese Zustandänderungen zu vermeiden, um sich nicht die Nachteile damit einzuhandeln.

Funktional mit Java. Wenn es darum geht nur mit Funktionen zu arbeiten, kommen Entwickler schnell zu einem Punkt, an dem Funktionen andere Funktionen als Argumente übergeben oder Funktionen zurückgeben. So etwas lässt sich in Java in der traditionellen Syntax nur sehr umständlich schreiben. Dies führt dazu, dass alles so unlesbar wird, dass der ganze Vorteil der kompakten deklarativen Schreibweise verloren geht.

Aus heutiger Sicht stellt sich eine Kombination aus beiden Konzepten als zukunftsweisend dar. Mit der in Java 8 eingeführten Schreibweise der Lambda-Ausdrücke sind funktionale Programme kompakt und relativ gut lesbar und die JVM hat gute Optimierungsmöglichkeiten. Java ermöglicht beide Programmierparadigmen und Entwickler können den Weg wählen, der für eine Problemlösung gerade am Besten ist. Diese Mehrdeutigkeit schafft natürlich auch Probleme, denn immer wenn es mehrere Lösungswege gibt, entstehen Auseinandersetzungen um die Beste der Varianten – und hier kann von Entwickler zu Entwickler eine konträre Meinung herrschen. Funktionale Programmierung hat unbestrittene Vorteile und das wollen wir uns genau anschauen.

Funktionale Programmierung in Java am Beispiel vom Comparator

Funktionale Programmierung hat auch daher etwas akademisches, weil in den Köpfen der Entwickler oftmals dieses Programmierparadigma nur mit mathematischen Funktionen in Verbindung gebracht wird. Und die wenigsten werden tatsächlich Fakultät oder Fibonacci-Zahlen in Programmen benötigen und daher schnell funktionale Programmierung beiseite legen. Doch diese Vorurteile sind unbegründet, und es ist hilfreich, funktionale Programmierung gedanklich von der Mathematik zu lösen, denn die allermeisten Programme haben nichts mit mathematischen Funktionen im eigentlichen Sinne zu tun, wohl aber viel stärker mit formal beschriebenen Methoden.

Betrachten wir erneut unser Beispiel aus der Einleitung, die Sortierung von Strings, diesmal aus der Sicht eines funktionalen Programmierers. Ein Comparator ist eine einfache „Funktion“, mit zwei Parametern und einer Rückgabe. Diese „Funktion“ (realisiert als Methode) wiederum wird an die sort(…)-Methode übergeben. Alles das ist funktionale Programmierung, denn wir programmieren Funktionen und übergeben sie. Drei Beispiele (Generics ausgelassen):

Code Bedeutung
Comparator c = (c1, c2) -> … Implementiert eine Funktion über Lambda-Ausdruck
Arrays.sort(T[] a, Comparator c) Nimmt eine Funktion als Argument an
Collections.reverseOrder(Comparator cmp) Nimmt eine Funktion an und liefert auch eine zurück

Beispiele für Funktionen in der Übergabe und als Rückgabe

Funktionen selbst können in Java nicht übergeben werden, also helfen sich Java-Entwickler mit der Möglichkeit, die Funktionalität in eine Methode zu setzen, sodass die Funktion zum Objekt mit einer Methode wird, was die Logik realisiert. Lambda-Ausdrücke bzw. Methoden/Konstruktor-Referenzen geben eine kompakte Syntax ohne den Ballast, extra eine Klasse mit einer Methoden schreiben zu müssen.

Der Typ Comparator ist eine funktionale Schnittstelle und steht für eine besondere Funktion mit zwei Parametern gleichen Typs und einer Ganzzahl-Rückgabe. Es gibt weitere funktionale Schnittstellen, die etwas flexibler sind als Comparator, in der Weise, dass etwa die Rückgabe statt int auch double oder etwas anderes sein können.

Lambda-Ausdrücke als Funktionen sehen

Wir haben gesehen, dass sich Lambda-Ausdrücke in einer Syntax formulieren lassen, die folgende allgemeine Form hat:

‘(‘ LambdaParameter ‘)’ ‘->’ ‘{‘ Anweisungen ‘}’

Der Pfeil macht gut deutlich, dass wir es bei Lambda-Ausdrücken mit Funktionen zu tun haben, die etwas abbilden. Im Fall vom Comparator ist es eine Abbildung von zwei Strings auf eine Ganzzahl; in einer etwas mathematischeren Notation gepackt: (String, String) → int.

Beispiel Methoden gibt es mit und ohne Rückgabe und mit und ohne Parameter. Genauso ist das mit  Lambda-Ausdrücken. Ein paar Beispiele in Java-Code mit ihren Abbildungen.

Lambda-Ausdruck Abbildung
(int a, int b) -> a + b (int, int) → int
(int a) -> Math.abs( a ) (int) → int
(String s) -> s.isEmpty() (String) → boolean
(Collection c) -> c.size() (Collection) → int
() -> Math.random() () → double
(String s) -> { System.out.print( s ); } (String) → void
() -> {} () → void

Lambda-Ausdrücke und was sie als Funktionen abbilden

Begriff: Funktion vs. Methode. Die Java Sprachdefinition kennt den Begriff „Funktion“ nicht, sondern spricht nur von Methoden. Methoden hängen immer an Klassen und das heißt, dass Methoden immer an einem Kontext hängen. Das ist zentral bei der Objektorientierung, da Methoden auf Attribute lesend und schreibend zugreifen können. Lambda-Ausdrücke wiederum realisieren Funktion, die erst einmal ihre Arbeitswerte rein aus den Parametern beziehen, sie hängen nicht an Klassen und Objekten. Der Gedanke bei funktionalen Programmiersprachen ist der, ohne Zustände auszukommen, also Funktionen so clever anzuwenden, dass sie ein Ergebnis liefern. Funktionen geben für eine spezifische Parameterkombination immer dasselbe Ergebnis zurück, unabhängig vom Zustand des umgebenden Gesamtprogramms.

Java Inseln bald in den Formaten PDF, EPUB und MOBI

Der Verlag hat lange an der digitalen Umsetzung für elektronische Lesegeräte gearbeitet. Die Inseln (und auch alle anderen Galileo-Bücher) wird es ab Ende Mai (also zur neuen Ausgabe für Java 8) als E-Books geben, und zwar in den Formaten PDF, EPUB und Kindle MOBI. Man wird dann das E-Book kaufen können (im Kauf sind alle Formate enthalten) oder man kauft die Print-Ausgabe und damit auch das E-Book (auch alle Formate). Wer bis dahin nicht warten kann muss das das Buch ganz normal herunterladen und mit (freien) Tools in das gewünschte Format konvertieren.

Konstruktor-Referenz von Java 8

Um ein Objekt aufzubauen, nutzen wir den new-Operator. Wenn wir new nutzen, dann wird ein Konstruktor aufgerufen, und optional lassen sich Argumente an den Konstruktor übergeben. Die Java-API deklariert aber auch Typen, von denen sich keine Exemplare mit new aufbauen lassen. Stattdessen gibt es Erzeuger, deren Aufgabe es ist, Objekte aufzubauen. Die Erzeuger können statische oder auch nicht statische Methoden sein.

Konstruktor … … erzeugt Erzeuger … … baut
new Integer( “1″ ) Integer Integer.valueOf( “1″ ) Integer
new File( “dir” ) File Paths.get( “dir” ) Path
new BigInteger( val ) BigInteger BigInteger.valueOf( val ) BigInteger

Beispiele für Konstruktoren und Erzeuger-Methoden

Beide, Konstruktoren und Erzeuger, lassen sich als spezielle Funktionen sehen, die von einem Typ in einen anderen Typ konvertieren. Damit eignen sie sich perfekt für Transformationen, und in einem Beispiel haben wir das schon eingesetzt:

Arrays.stream( words )
      . …
      .map( Integer::parseInt )
      . …

Integer.parseInt(string) ist eine Methode, die sich einfach mit einer Methoden-Referenz fassen lässt, und zwar als Integer::parseInt. Aber was ist mit Konstruktoren? Auch sie transformieren! Statt Integer.parseInt(string) hätte ja auch new Integer(string) eingesetzt werden können.

Wo Methoden-Referenzen statische Methoden und Objekt-Methoden angeben können, bieten Konstruktor-Referenzen die Möglichkeit, Konstruktoren anzugeben, sodass diese als Erzeuger an anderer Stelle übergeben werden können. Damit lassen sich elegant Konstruktoren als Erzeuger angeben, und zwar auch von einer Klasse, die nicht über Erzeuger-Methoden verfügt. Wie auch bei Methoden-Referenzen spielt eine funktionale Schnittstelle eine entscheidende Rolle, doch dieses Mal ist es die Methode der funktionalen Schnittstelle, die mit ihrem Aufruf zum Konstruktor-Aufruf führt. Wo syntaktisch bei Methoden-Referenzen rechts vom Doppelpunkt ein Methodenname steht, ist dies bei Konstruktor-Referenzen ein new.[1]  Also ergibt sich alternativ zu

      .map( Integer::parseInt )       // Methode Integer.parseInt(String)

in unserem Beispiel das Ergebnis mittels:

      .map( Integer::new )            // Konstruktor Integer(String)

Mit der Konstruktor-Referenz gibt es also vier Möglichkeiten funktionale Schnittstellen zu implementieren; die drei verbleibenden Varianten sind Lambda-Ausdrücke, Methoden-Referenzen und klassischer Implementierung über eine Klasse.

Beispiel: Die funktionale Schnittstelle sei:

interface DateFactory { Date create(); }

Die folgende Konstruktor-Referenz bindet den Konstruktor an die Methode create() der funktionalen Schnittstelle:

DateFactory factory = Date::new;
System.out.print( factory.create() ); // z.B. Sat Dec 29 09:56:35 CET 2012

Bzw. die letzten beiden Zeilen zusammengefasst:

System.out.println( ((DateFactory)(Date::new)).create() );

Soll nur der Standard-Konstruktor aufgerufen werden, muss die funktionale Schnittstelle nur eine Methode besitzen, die keinen Parameter besitzt und etwas zurückliefert. Der Rückgabetyp der Methode muss natürlich mit dem Klassentyp zusammen passen. Das gilt für den Typ DateFactory aus unserem Beispiel. Doch es geht noch etwas generischer, zum Beispiel mit der vorhandenen funktionalen Schnittstelle Supplier, wie wir gleich sehen werden.

In der API finden sich oftmals Parameter vom Typ Class, die als Typ-Angabe dazu verwendet werden, dass die Methode mit newInstance() Exemplare bilden kann. Der Einsatz von Class lässt sich durch eine funktionale Schnittstelle ersetzen, und Konstruktor-Referenzen lassen sich anstelle von Class-Objekten übergeben.

Standard- und parametrisierte Konstruktoren

Beim Standard-Konstruktor hat die Methode nur eine Rückgabe, bei einem parametrisierten Konstruktor muss die Methode der funktionalen Schnittstelle natürlich über eine kompatible Parameterliste verfügen.

Konstruktor Date() Date(long t)
Kompatible funktionale Schnittstelle interface DateFactory {

Date create();

}

interface DateFactory {

Date create(long t);

}

Konstruktor-Referenz DateFactory factory =

Date::new;

DateFactory factory =

Date::new;

Aufruf factory.create(); factory.create(1);

Standard- und parametrisierter Konstruktor mit korrespondierenden funktionalen Schnittstellen

Hinweis: Kommt die Typ-Inferenz des Compilers an ihre Grenzen, sind zusätzliche Typinformationen gefordert. In diesem Fall werden hinter dem Doppelpunkt in eckigen Klammen weitere Angaben gemacht, etwa Klasse::<Typ1, Typ2>new.

Nützliche vordefinierte Schnittstellen für Konstruktor-Referenzen

Die für einen Standard-Konstruktor passende funktionale Schnittstelle muss eine Rückgabe besitzen und keinen Parameter annehmen; die funktionale Schnittstelle für einen parametrisierten Konstruktor muss eine entsprechende Parameterliste haben. Es kommt nun häufig vor, dass der Konstruktor ein Standard-Konstruktor ist oder genau einen Parameter annimmt. Hier ist es vorteilhaft, dass für diese beiden Fälle die Java API zwei praktische (generisch deklarierte) funktionale Schnittstellen mitbringt:

Funktionale Schnittstelle Funktions-Deskriptor Abbildung Passt auf
Supplier<T> T get() () -> T Standard-Konstruktor
Function<T, R> R apply(T t) (T) -> R einfachen parametrisierten Konstruktor

Vorhandene funktionale Schnittstellen als Erzeuger

Beispiel: Die funktionale Schnittstelle Supplier<T> hat eine T get()-Methode, die wir mit dem Standard-Konstruktor von Date verbinden können:

Supplier<Date> factory = Date::new;
System.out.print( factory.get() );

Wir nutzen Supplier mit dem Typparameter Date, was den parametrisierten Typ Supplier<Date> ergibt, und get() liefert folglich den Typ Date. Der Aufruf factory.get() führt zum Aufruf des Konstruktors.

Ausblick *

Besonders interessant werden die Konstruktor-Referenzen mit den neuen Bibliotheksmethoden von Java 8. Nehmen wir eine Liste vom Typ Zeitstempel an. Der Konstruktor Date(long) nimmt einen solchen Zeitstempel entgegen, und mit einem Date-Objekt können wir Vergleiche vornehmen, etwa, ob ein Datum hinter einem anderen Datum liegt. Folgendes Beispiel listet alle Datumswerte auf, die nach dem 1.1.2012 liegen:

Long[] timestamps = { 2432558632L, 1455872986345L };
Date thisYear = new GregorianCalendar( 2012, Calendar.JANUARY, 1 ).getTime();
Arrays.stream( timestamps )
      .map( Date::new )
      .filter( thisYear::before )
      .forEach( System.out::println );  // Fri Feb 19 10:09:46 CET 2016

Die Konstruktor-Referenz Date::new hilft dabei, das long mit dem Zeitstempel in ein Date-Objekt zu konvertieren.

Denksportaufgabe: Ein Konstruktor kann als Supplier bzw. Function gelten. Problematisch sind mal wieder geprüfte Ausnahmen. Der Leser soll überlegen, ob der Konstruktor URI(String str) throws URISyntaxException über URI::new angesprochen werden kann.



[1]              Da new ein Schlüsselwort ist, kann keine Methode so heißen; der Identifizierer ist also sicher.

Methoden-Referenz von Java 8

Je größer Software-Systeme werden, desto wichtiger werden Dinge wie Klarheit, Wiederverwendbarkeit und Dokumentation. Wir haben für unseren String-Comparator eine Implementierung geschrieben, anfangs über eine innere Klasse, später über einen Lambda-Ausdruck. In jedem Fall haben wir Code geschrieben. Doch was wäre, wenn eine Utility-Klasse schon eine Implementierung mitbringen würde? Dann könnte der Lambda-Ausdruck natürlich an die vorhandene Implementierung delegieren, und wir sparen Code. Schauen wir uns das mal an einem Beispiel an:

class StringUtils {
  public static int compareTrimmed( String s1, String s2 ) {
    return s1.trim().compareTo( s2.trim() );
  }    
}

public class CompareIgnoreCase {
  public static void main( String[] args ) {
    String[] words = { "A", "B", "a" };
      Arrays.sort( words, (String s1, String s2) -> 
StringUtils.compareTrimmed(s1, s2) );
      System.out.println( Arrays.toString( words ) );
  }
}

Auffällig ist hier, dass die referenzierte Methode compareTrimmed(String,String) von den Parametertypen und vom Rückgabetyp genau auf die compare(…)-Methode eines Comparator passt. Für genau solche Fälle gibt es eine weitere syntaktische Verkürzung, so dass im Code kein Lambda-Ausdruck, sondern nur noch ein Methodenverweis notwendig ist.

Definition: Eine Methoden-Referenz ist  ein Verweis auf  eine Methode ohne diese jedoch aufzurufen. Syntaktisch trennen zwei Doppelpunkte den Klassenamen bzw. die Referenz auf der linken Seite von dem Methodennamen auf der rechten.

Die Zeile

Arrays.sort( words, (String s1, String s2) -> StringUtils.compareTrimmed(s1, s2) );

lässt sich mit einer Methoden-Referenzen abkürzen zu:

Arrays.sort( words, StringUtils::compareTrimmed );

Die Sortiermethode erwartet vom Comparator eine Methode, die zwei Strings annimmt und eine Ganzzahl zurückgibt. Der Name der Klasse und der Name der Methode sind unerheblich, weshalb an dieser Stelle eine Methoden-Referenz eingesetzt werden kann.

Eine Methoden-Referenz ist wie ein Lambda-Ausdruck ein Exemplar einer funktionalen Schnittstelle, jedoch für eine existierende Methode einer bekannten Klasse. Wie üblich bestimmt der Kontext von welchem Typ genau der Ausdruck ist.

Hinweis: Gleicher Code für eine Methoden-Referenz kann zu komplett unterschiedlichen Typen führen – der Kontext macht den Unterschied:

Comparator<String>                  c1 = StringUtils::compareTrimmed;
BiFunction<String, String, Integer> c2 = StringUtils::compareTrimmed;

Varianten von Methoden-Referenzen

Im Beispiel ist die Methode compareTrimmed(…) statisch, und links vom Doppelpunkt steht der Name eines Typs. Allerdings kann beim Einsatz eines Typnamen die Methode auch nicht-statisch sein, String::length ist so ein Beispiel. Das wäre eine Funktion, die ein String auf ein int abbildet, in Code: Function<String, Integer> len = String::length;.

Links von den zwei Doppelpunkten kann auch eine Referenz stehen, was dann immer eine Objektmethode referenziert.

Beispiel: Während String::length eine Funktion ist, wäre string::length ein Supplier, unter der Annahme, das string eine Referenzvariable ist:

String string = "Goll";
Supplier<Integer> len = string::length;
System.out.println( len.get() );     // 4

System.out ist eine Referenz und eine Methode wie println(…) kann an einen Consumer gebunden werden. Es ist aber auch ein Runnable, weil es println() auch ohne Parameterliste gibt.

Consumer<String> out = System.out::println;
out.accept( "Kates kurze Kleider" );
Runnable out = System.out::println;
out.run();

Ist eine Hauptmethode mit main(String… args) deklariert, so ist das auch ein Runnable:

Runnable r = JavaApplication1::main;

Anderes wäre das bei main(String[]), hier ist ein Parameter zwingend, doch ein Vararg kann auch leer sein.

Statt dass der Name einer Referenzvariablen gewählt wird, kann auch this das Objekt beschreiben und auch super ist möglich. this ist praktisch, wenn die Implementierung einer funktionalen Schnittstelle auf eine Methode der eigenen Klasse delegieren möchte. Wenn zum Beispiel eine lokale Methode compareTrimmed(…) in der Klassen existieren würde, in der auch der Lambda-Ausdruck steht,  und sollte diese Methode als Comparator in Arrays.sort(…) verwendet werden, könnte es heißen: Arrays.sort(words, this::compareTrimmed).

Hinweis: Es ist nicht möglich eine spezielle Methode über die Methodenreferenz auszuwählen. Eine Angabe wie String::valueOf oder Arrays::sort ist relativ breit – bei letzterem wählt der Compiler eine der 18 passenden überladen Methoden aus. Da kann es passieren, dass der Compiler eine falsche Methode auswählt, in dem Fall muss ein expliziter Lambda-Ausdruck eine Mehrdeutigkeit auflösen. Bei generischen Typen kann zum Beispiel List<String>::length oder auch List::length stehen auch hier erkennt der Compiler wieder alles selbst.

Was soll das alles?

Einem Einsteiger in die Sprache Java wird dieses Sprache-Feature wie der größte Zauber auf Erden vorkommen, und auch Java-Profis bekommen hier zittrige Finger, entweder vor Furcht oder Aufregung… In der Vergangenheit musste in Java sehr viel Code explizit geschrieben werden, aber mit diesen neuen Methoden-Referenzen erkennt und macht der Compiler vieles von selbst.

Nützlich wird diese Eigenschaft mit den funktionalen Bibliotheken aus Java 8, die ein eigenes Kapitel einnehmen. Hier nur ein kurzer Vorgeschmack:

Object[] words = { " ", '3', null, "2", 1, "" };
Arrays.stream( words )
      .filter( Objects::nonNull )
      .map( Objects::toString )
      .map( String::trim )
      .filter( s -> ! s.isEmpty() )
      .map( Integer::parseInt )
      .sorted()
      .forEach( System.out::println );   // 1 2 3

Klassen mit einer abstrakten Methode als funktionale Schnittstelle?

Als die Entwickler der Sprache Java die Lambda-Ausdrücke diskutierten, stand auch die Frage im Raum, ob abstrakte Klassen, die nur über eine abstrakte Methode verfügen, ebenfalls für Lambda-Ausdrücke genutzt werden können.[1] Sie entschieden sich dagegen, unter anderem deswegen, weil bei der Implementierung von Schnittstellen die JVM weitreichende Optimierungen vornehmen kann. Und bei Klassen wird das schwierig. Das liegt auch daran, dass ein Konstruktor umfangreiche Initialisierungen mit Seiteneffekten vornimmt (die Konstruktoren aller Oberklassen nicht zu vergessen), sowie Ausnahmen auslösen könnte. Gewünscht ist aber nur die Ausführung einer Implementierung der funktionalen Schnittstelle und kein anderer Code.

Es gibt nun im JDK einige abstrakte Klassen, die genau eine abstrakte Methode vorschreiben, etwa java.util.TimerTask. Solche Klassen können nicht über einen Lambda-Ausdruck realisiert werden; hier müssen Entwickler weiterhin zu Klassenimplementierungen greifen, und die kürzeste Lösung ist eine innere anonyme Klasse. Eigene Hilfsklassen können natürlich den Code etwas abkürzen, aber eben nur mit Hilfe einer eigenen Implementierung.

Wer abstrakte Methoden mit Lambda-Ausdrücken implementieren möchte, kann mit Hilfsklassen arbeiten. Denn wenn eine Hilfsklasse funktionale Schnittstellen einsetzt, so können Lambda-Ausdrücke wieder ins Spiel kommen, in dem die Implementierung der abstrakten Methode an den Lambda-Ausdruck weiterleitet. Nehmen wir das Beispiel für TimerTask und gehen zwei unterschiedliche Strategien der Implementierung durch. Mit Delegation sieht das so aus:

import java.util.*;

class TimerTaskLambda {

  public static TimerTask createTimerTask( Runnable runnable ) {
    return new TimerTask() {
        @Override public void run() { runnable.run(); }
    };
  }
 
  public static void main( String[] args ) {
    new Timer().schedule( createTimerTask( () -> System.out.println("Hi") ), 500 );
  }
}

Mit Vererbung erhalten wir:

public class LambdaTimerTask extends TimerTask {
  private final Runnable runnable;
    public LambdaTimerTask( Runnable runnable ) {
    this.runnable = runnable;
  }
   
  @Override public void run() { runnable.run(); }
}

Der Aufruf erfolgt dann statt createTimerTask(…) mit dem Konstruktor:

new Timer().schedule( new LambdaTimerTask( () -> System.out.println("Hi") ), 500 );

[1]              Früher wurde hier die Abkürzung SAM (Single Abstract Method) genutzt.