Warum unterstützen Lieferanten nur Konstrukteure ohne Argumente?
Wenn der Standardkonstruktor vorhanden ist, kann ich Folgendes tun:
create(Foo::new)
Aber wenn der einzige Konstruktor einen String nimmt, muss ich Folgendes tun:
create(() -> new Foo("hello"))
Supplier
tut Arbeit mit gelieferten Argumente, dh wenn ein Lambda - Ausdruck verwendet wird . Ihre eigentliche Frage scheint also zu sein: "Warum funktioniert eine Methodenreferenz nur, wenn die Funktionsparameter mit den Zielparametern übereinstimmen?" Und die Antwort lautet, denn dafür sind Methodenreferenzen gedacht. Wenn die Parameterliste nicht übereinstimmt, verwenden Sie einen Lambda-Ausdruck, wie Sie bereits in Ihrer Frage gezeigt haben. Denn dafür ist Lambda-Ausdruck gedacht (nicht ausschließlich)…Antworten:
Dies ist nur eine Einschränkung der Methodenreferenzsyntax - Sie können keines der Argumente übergeben. So funktioniert die Syntax.
quelle
Ein 1-Argument-Konstruktor dafür
T
benötigtString
jedoch FolgendesFunction<String,T>
:Function<String, Foo> fooSupplier = Foo::new;
Welcher Konstruktor ausgewählt wird, wird basierend auf der Form des Zieltyps als Überlastungsauswahlproblem behandelt.
quelle
Wenn Sie Methodenreferenzen so sehr mögen, können Sie eine
bind
Methode selbst schreiben und verwenden:public static <T, R> Supplier<R> bind(Function<T,R> fn, T val) { return () -> fn.apply(val); } create(bind(Foo::new, "hello"));
quelle
Die
Supplier<T>
Schnittstelle stellt eine Funktion mit der Signatur von dar() -> T
, dh sie akzeptiert keine Parameter und gibt etwas vom Typ zurückT
. Methodenreferenzen, die Sie als Argumente angeben, müssen dieser Signatur folgen, um übergeben zu werden.Wenn Sie eine erstellen möchten,
Supplier<Foo>
die mit dem Konstruktor zusammenarbeitet, können Sie die von @Tagir Valeev vorgeschlagene allgemeine Bindemethode verwenden oder eine speziellere erstellen.Wenn Sie einen String möchten
Supplier<Foo>
, der diesen"hello"
String immer verwendet, können Sie ihn auf zwei verschiedene Arten definieren: als Methode oder alsSupplier<Foo>
Variable.Methode:
static Foo makeFoo() { return new Foo("hello"); }
Variable:
static Supplier<Foo> makeFoo = () -> new Foo("hello");
Sie können die Methode mit einer Methodenreferenz (
create(WhateverClassItIsOn::makeFoo);
) übergeben, und die Variable kann einfach unter Verwendung des Namens übergeben werdencreate(WhateverClassItIsOn.makeFoo);
.Die Methode ist etwas vorzuziehen, da sie außerhalb des Kontextes der Übergabe als Methodenreferenz einfacher zu verwenden ist und auch in dem Fall verwendet werden kann, dass jemand eine eigene spezielle Funktionsschnittstelle benötigt, die ebenfalls
() -> T
oder() -> Foo
spezifisch ist .Wenn Sie einen verwenden möchten, der einen
Supplier
beliebigen String als Argument verwenden kann, sollten Sie so etwas wie die erwähnte Bindemethode @Tagir verwenden und die Notwendigkeit umgehen, Folgendes anzugebenFunction
:Supplier<Foo> makeFooFromString(String str) { return () -> new Foo(str); }
Sie können dies als Argument wie folgt übergeben:
create(makeFooFromString("hello"));
Vielleicht sollten Sie jedoch alle "Make ..." - Anrufe in "Supply ..." - Anrufe ändern, um es ein wenig klarer zu machen.
quelle
Da ein 1-Argument-Konstruktor zu einer SAM-Schnittstelle mit 1 Argument und 1 Rückgabewert isomorph ist, z. B.
java.util.function.Function<T,R>
'sR apply(T)
.Andererseits ist
Supplier<T>
sT get()
isomorph zu einem Null-Arg-Konstruktor.Sie sind einfach nicht kompatibel. Entweder muss Ihre
create()
Methode polymorph sein, um verschiedene funktionale Schnittstellen zu akzeptieren und je nach den angegebenen Argumenten unterschiedlich zu handeln, oder Sie müssen einen Lambda-Körper schreiben, der als Klebercode zwischen den beiden Signaturen fungiert.Was ist Ihre unerfüllte Erwartung hier? Was soll Ihrer Meinung nach passieren?
quelle
Koppeln Sie den Lieferanten mit einem FunctionalInterface.
Hier ist ein Beispielcode, den ich zusammengestellt habe, um das "Binden" einer Konstruktorreferenz an einen bestimmten Konstruktor mit Function sowie verschiedene Möglichkeiten zum Definieren und Aufrufen der "Factory" -Konstruktorreferenzen zu demonstrieren.
import java.io.Serializable; import java.util.Date; import org.junit.Test; public class FunctionalInterfaceConstructor { @Test public void testVarFactory() throws Exception { DateVar dateVar = makeVar("D", "Date", DateVar::new); dateVar.setValue(new Date()); System.out.println(dateVar); DateVar dateTypedVar = makeTypedVar("D", "Date", new Date(), DateVar::new); System.out.println(dateTypedVar); TypedVarFactory<Date, DateVar> dateTypedFactory = DateVar::new; System.out.println(dateTypedFactory.apply("D", "Date", new Date())); BooleanVar booleanVar = makeVar("B", "Boolean", BooleanVar::new); booleanVar.setValue(true); System.out.println(booleanVar); BooleanVar booleanTypedVar = makeTypedVar("B", "Boolean", true, BooleanVar::new); System.out.println(booleanTypedVar); TypedVarFactory<Boolean, BooleanVar> booleanTypedFactory = BooleanVar::new; System.out.println(booleanTypedFactory.apply("B", "Boolean", true)); } private <V extends Var<T>, T extends Serializable> V makeVar(final String name, final String displayName, final VarFactory<V> varFactory) { V var = varFactory.apply(name, displayName); return var; } private <V extends Var<T>, T extends Serializable> V makeTypedVar(final String name, final String displayName, final T value, final TypedVarFactory<T, V> varFactory) { V var = varFactory.apply(name, displayName, value); return var; } @FunctionalInterface static interface VarFactory<R> { // Don't need type variables for name and displayName because they are always String R apply(String name, String displayName); } @FunctionalInterface static interface TypedVarFactory<T extends Serializable, R extends Var<T>> { R apply(String name, String displayName, T value); } static class Var<T extends Serializable> { private String name; private String displayName; private T value; public Var(final String name, final String displayName) { this.name = name; this.displayName = displayName; } public Var(final String name, final String displayName, final T value) { this(name, displayName); this.value = value; } public void setValue(final T value) { this.value = value; } @Override public String toString() { return String.format("%s[name=%s, displayName=%s, value=%s]", getClass().getSimpleName(), this.name, this.displayName, this.value); } } static class DateVar extends Var<Date> { public DateVar(final String name, final String displayName) { super(name, displayName); } public DateVar(final String name, final String displayName, final Date value) { super(name, displayName, value); } } static class BooleanVar extends Var<Boolean> { public BooleanVar(final String name, final String displayName) { super(name, displayName); } public BooleanVar(final String name, final String displayName, final Boolean value) { super(name, displayName, value); } } }
quelle
Bei der Suche nach einer Lösung für das parametrisierte
Supplier
Problem fand ich die obigen Antworten hilfreich und wandte die Vorschläge an:private static <T, R> Supplier<String> failedMessageSupplier(Function<String,String> fn, String msgPrefix, String ... customMessages) { final String msgString = new StringBuilder(msgPrefix).append(" - ").append(String.join("\n", customMessages)).toString(); return () -> fn.apply(msgString); }
Es wird folgendermaßen aufgerufen:
failedMessageSupplier(String::new, msgPrefix, customMsg);
Noch nicht ganz zufrieden mit dem reichlich vorhandenen statischen Funktionsparameter, habe ich weiter gegraben und mit Function.identity () zu folgendem Ergebnis gekommen:
private final static Supplier<String> failedMessageSupplier(final String msgPrefix, final String ... customMessages) { final String msgString = new StringBuilder(msgPrefix).append(" - ").append(String.join("\n", customMessages)).toString(); return () -> (String)Function.identity().apply(msgString); };
Aufruf jetzt ohne den statischen Funktionsparameter:
Da
Function.identity()
eine Funktion des Typs zurückgegebenObject
wird und auch der nachfolgende Aufruf vonapply(msgString)
, ist eine Umwandlung inString
erforderlich - oder was auch immer der Typ ist, mit apply () wird gefüttert.Diese Methode ermöglicht beispielsweise die Verwendung mehrerer Parameter, die dynamische Zeichenfolgenverarbeitung, Zeichenfolgenkonstantenpräfixe, Suffixe usw.
Die Verwendung von Identität sollte theoretisch auch einen leichten Vorteil gegenüber String :: new haben, wodurch immer ein neuer String erstellt wird.
Wie Jacob Zimmerman bereits betonte, ist die einfachere parametrisierte Form
Supplier<Foo> makeFooFromString(String str1, String str2) { return () -> new Foo(str1, str2); }
ist immer möglich. Ob dies in einem Kontext sinnvoll ist oder nicht, hängt davon ab.
Wie auch oben beschrieben, erfordern statische Methodenreferenzaufrufe die Anzahl und Art der Rückgabe / Parameter der entsprechenden Methode, um mit denen übereinzustimmen, die von der funktionsaufwendigen (Stream-) Methode erwartet werden.
quelle
Wenn Sie einen Konstruktor für haben
new Klass(ConstructorObject)
, können Sie Folgendes verwendenFunction<ConstructorObject, Klass>
:interface Interface { static Klass createKlass(Function<Map<String,Integer>, Klass> func, Map<String, Integer> input) { return func.apply(input); } } class Klass { private Integer integer; Klass(Map<String, Integer> map) { this.integer = map.get("integer"); } public static void main(String[] args) { Map<String, Integer> input = new HashMap<>(); input.put("integer", 1); Klass klazz = Interface.createKlass(Klass::new, input); System.out.println(klazz.integer); } }
quelle