Konstruktor-Referenz in 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 statische (oder nicht-statische) Erzeuger, deren Aufgabe es ist, Objekte aufzubauen.

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 Erzeugen, lassen sich als spezielle Funktionen sehen, den von einem Typ in einem 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 und Objekt-Methoden angeben können, so bieten Konstruktor-Referenzen die Möglichkeit, Konstruktoren anzugeben, sodass diese als Erzeuger an anderer Stelle übergeben werden können. Damit lassen sich elegant Erzeuger angeben, auch wenn diese nicht über Erzeuger-Methoden verfügen. Wie auch bei Methoden-Referenzen spielt eine funktionale Schnittstelle eine entschiedene Rolle, doch dieses Mal ist es die Methode der funktionalen Schnittstelle, die aufgerufen zum Konstruktor-Aufruf führt. Wo syntaktisch bei Methoden-Referenzen rechts vom Doppelpunkt ein Methodenname steht, steht bei Konstruktor-Referenzen new.[1]

Beispiel: Die funktionale Schnittstelle sei:

interface DateFactory { Date create(); }

Die 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. Das gilt für unseren eigenen Typ DateFactory, 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. 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 dem 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 funktionale Schnittstelle passend für einen Standard-Konstruktor muss eine Rückgabe besitzen und keinen Parameter annehmen; die funktionale Schnittstelle für 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 kommt es entgegen, dass für diesen beiden Fälle die Java API zwei praktische (generische 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 parametrisierter Konstruktor

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 *

Interessant werden die Konstruktor-Referenzen wieder mit den Möglichkeiten von Java 8. Nehmen wir eine Liste von Zeitstempel an. Der Konstruktor Date(long) nimmt einen solchen Zeitstempel an 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

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

Ähnliche Beiträge

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert