12.3 Konstruktorreferenz
Um ein Objekt aufzubauen, nutzen wir das Schlüsselwort new. Das führt zum Aufruf eines Konstruktors, dem sich optional Argumente übergeben lassen. Die Java-API deklariert aber auch Typen, von denen sich keine direkten Exemplare mit new aufbauen lassen. Stattdessen gibt es Erzeuger, deren Aufgabe es ist, Objekte aufzubauen. Die Erzeuger können statische oder auch nichtstatische Methoden sein:
… 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 |
Beide, Konstruktoren und Erzeuger, lassen sich als spezielle Funktionen betrachten, 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 Methodenreferenz 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 Methodenreferenzen statische Methoden und Objektmethoden angeben können, bieten Konstruktorreferenzen 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 Erzeugermethoden verfügt. Wie auch bei Methodenreferenzen spielt eine funktionale Schnittstelle eine entscheidende Rolle. Doch dieses Mal ist es die Methode der funktionalen Schnittstelle, die mit ihrem Aufruf zum Konstruktoraufruf führt. Wo syntaktisch bei Methodenreferenzen rechts vom Doppelpunkt ein Methodenname steht, ist dies bei Konstruktorreferenzen ein new.[ 223 ](Da new ein Schlüsselwort ist, kann keine Methode so heißen; der Identifizierer ist also sicher. ) 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 Konstruktorreferenz gibt es vier Möglichkeiten, funktionale Schnittstellen zu implementieren. Die drei verbleibenden Varianten sind Lambda-Ausdrücke, Methodenreferenzen und klassische Implementierung über eine Klasse.
[zB] Beispiel
Die funktionale Schnittstelle sei:
interface DateFactory { Date create(); }
Die folgende Konstruktorreferenz bindet den Konstruktor an die Methode create() der funktionalen Schnittstelle:
DateFactory factory = Date::new;
System.out.print( factory.create() ); // zum Beispiel Fri Oct 06 22:34:24 CET 2017
Die letzten beiden Zeilen lassen sich auch so zusammenfassen:
System.out.println( ((DateFactory)Date::new).create() );
Soll nur der parameterlose 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 zusammenpassen. 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 Typangabe dazu verwendet werden, dass über den Constructor der Class mit der Methode newInstance() Exemplare gebildet werden. Der Einsatz von Class lässt sich durch eine funktionale Schnittstelle ersetzen, und Konstruktorreferenzen lassen sich anstelle von Class-Objekten übergeben.
12.3.1 Parameterlose und parametrisierte Konstruktoren
Beim parameterlosen 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 { | interface DateFactory { |
Konstruktorreferenz | DateFactory factory = | DateFactory factory = |
Aufruf | factory.create(); | factory.create(1); |
[»] Hinweis
Kommt die Typ-Inferenz des Compilers an ihre Grenzen, sind zusätzliche Typinformationen gefordert. In diesem Fall werden hinter dem Doppelpunkt in eckigen Klammern weitere Angaben gemacht, etwa Klasse::<Typ1, Typ2>new.
12.3.2 Nützliche vordefinierte Schnittstellen für Konstruktorreferenzen
Die für einen parameterlosen 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 parameterloser 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 | Funktions- | Abbildung | Passt auf |
---|---|---|---|
Supplier<T> | T get() | () → T | parameterlosen Konstruktor |
Function<T,R> | R apply(T t) | (T) → R | einfachen parametrisierten Konstruktor |
[zB] Beispiel
Die funktionale Schnittstelle Supplier<T> hat eine T get()-Methode, die wir mit dem parameterlosen 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 Konstruktorreferenzen mit den neuen Bibliotheksmethoden der Stream-API. 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.2020 liegen:
Long[] timestamps = { 2432558632L, // Thu Jan 29 04:42:38 CET 1970
1609455600000L }; // Fri Jan 01 00:00:00 CET 2021
Date givenYear = new GregorianCalendar( 2000, Calendar.JANUARY, 1 ).getTime();
Arrays.stream( timestamps )
.map( Date::new )
.filter( givenYear::before )
.forEach( System.out::println ); // Fri Jan 01 00:00:00 CET 2021
Die Konstruktorreferenz Date::new hilft dabei, das long mit dem Zeitstempel in ein Date-Objekt zu konvertieren.
[»] Denksportaufgabe
Ein Konstruktor kann als Supplier oder 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.