Java 8 Lieferant mit Argumenten im Konstruktor


81

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"))

9
Wie könnte der Compiler erraten, dass das Argument "Hallo" sein soll?
Assylias

6
Ihre Frage macht einfach keinen Sinn. Sie schreiben : „Warum ohne argumentBauer Lieferanten nur Arbeit machen?“, Dann sind Sie selbst beweisen , dass ein 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)…
Holger

Antworten:


62

Dies ist nur eine Einschränkung der Methodenreferenzsyntax - Sie können keines der Argumente übergeben. So funktioniert die Syntax.


69

Ein 1-Argument-Konstruktor dafür Tbenötigt Stringjedoch Folgendes Function<String,T>:

Function<String, Foo> fooSupplier = Foo::new;

Welcher Konstruktor ausgewählt wird, wird basierend auf der Form des Zieltyps als Überlastungsauswahlproblem behandelt.


46

Wenn Sie Methodenreferenzen so sehr mögen, können Sie eine bindMethode 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"));

14

Die Supplier<T>Schnittstelle stellt eine Funktion mit der Signatur von dar () -> T, dh sie akzeptiert keine Parameter und gibt etwas vom Typ zurück T. 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 als Supplier<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 werden create(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 () -> Toder () -> Foospezifisch ist .

Wenn Sie einen verwenden möchten, der einen Supplierbeliebigen String als Argument verwenden kann, sollten Sie so etwas wie die erwähnte Bindemethode @Tagir verwenden und die Notwendigkeit umgehen, Folgendes anzugeben Function:

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.


12

Warum arbeiten Lieferanten nur mit Konstruktoren ohne Argumente zusammen?

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>'s R apply(T).

Andererseits ist Supplier<T>s T 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?


3
Dies wäre eine bessere Antwort, wenn es mit etwas mehr Nachdruck auf Kommunikation geschrieben würde. Sowohl "isomorphe" als auch "SAM-Schnittstelle" im ersten Satz zu haben, scheint ein Overkill für eine Site zu sein, die existiert, um Menschen mit etwas zu helfen, das sie nicht verstehen.
L. Blanc

1

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);
        }
    }
}

1

Bei der Suche nach einer Lösung für das parametrisierte SupplierProblem 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:

failedMessageSupplier(msgPrefix, customMsg)

Da Function.identity()eine Funktion des Typs zurückgegeben Objectwird und auch der nachfolgende Aufruf von apply(msgString), ist eine Umwandlung in Stringerforderlich - 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.


0

Wenn Sie einen Konstruktor für haben new Klass(ConstructorObject), können Sie Folgendes verwenden Function<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);
    }
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.