Java 8-leverancier met argumenten in de constructor

Waarom ondersteunen leveranciers alleen no-arg-constructors?

Als de standaardconstructor aanwezig is, kan ik dit doen:

create(Foo::new)

Maar als de enige constructor een String neemt, moet ik dit doen:

create(() -> new Foo("hello"))

Antwoord 1, autoriteit 100%

Maar een 1-arg constructor voor Tdie een Stringnodig heeft, is compatibel met Function<String,T>:

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

Welke constructor is geselecteerd, wordt behandeld als een probleem voor de selectie van overbelasting, gebaseerd op de vorm van het doeltype.


Antwoord 2, autoriteit 80%

Dat is slechts een beperking van de syntaxis van de methodeverwijzing — die u niet kunt invoeren in een van de argumenten. Het is gewoon hoe de syntaxis werkt.


Antwoord 3, autoriteit 67%

Als je zo van methodereferenties houdt, kun je zelf een bind-methode schrijven en gebruiken:

public static <T, R> Supplier<R> bind(Function<T,R> fn, T val) {
    return () -> fn.apply(val);
}
create(bind(Foo::new, "hello"));

Antwoord 4, autoriteit 21%

De Supplier<T>interface vertegenwoordigt een functie met een handtekening van () -> T, wat betekent dat het geen parameters nodig heeft en iets van het type Tteruggeeft. Methodeverwijzingen die u als argumenten opgeeft, moeten die handtekening volgen om te worden doorgegeven.

Als u een Supplier<Foo>wilt maken die werkt met de constructor, kunt u de algemene bindmethode gebruiken die @Tagir Valeev voorstelt, of u maakt een meer gespecialiseerde.

>

Als u een Supplier<Foo>wilt die altijd die "hello"-string gebruikt, kunt u deze op twee verschillende manieren definiëren: als methode of als Supplier<Foo>variabele.

methode:

static Foo makeFoo() { return new Foo("hello"); }

variabele:

static Supplier<Foo> makeFoo = () -> new Foo("hello");

Je kunt de methode doorgeven met een methodereferentie(create(WhateverClassItIsOn::makeFoo);), en de variabele kan eenvoudig worden doorgegeven met de naam create(WhateverClassItIsOn.makeFoo);.

De methode heeft iets meer de voorkeur omdat het gemakkelijker te gebruiken is buiten de context van doorgegeven worden als een methodeverwijzing, en het kan ook worden gebruikt in het geval dat iemand zijn eigen gespecialiseerde functionele interface nodig heeft die ook () -> Tof is () -> Foospecifiek.

Als je een Supplierwilt gebruiken die elke String als argument kan gebruiken, moet je zoiets als de genoemde bindmethode @Tagir gebruiken, waarbij je de noodzaak om de Function:

Supplier<Foo> makeFooFromString(String str) { return () -> new Foo(str); }

Je kunt dit als argument doorgeven: create(makeFooFromString("hello"));

Hoewel, misschien moet je alle “make…”-oproepen veranderen in “supply…”-oproepen, om het een beetje duidelijker te maken.


Antwoord 5, autoriteit 17%

Waarom werken leveranciers alleen met no-arg constructeurs?

Omdat een 1-arg-constructor isomorf is met een SAM-interface met 1 argument en 1 retourwaarde, zoals java.util.function.Function<T,R>‘s R apply(T).

Aan de andere kant is Supplier<T>van Supplier<T>isomorf met een nul arg-constructor.

Ze zijn gewoon niet compatibel. Ofwel je create()-methode moet polymorf zijn om verschillende functionele interfaces te accepteren en anders te handelen, afhankelijk van welke argumenten worden aangeleverd, of je moet een lambda-body schrijven om als lijmcode tussen de twee handtekeningen te fungeren.

Wat is uw onvervulde verwachting hier? Wat moetgebeuren volgens jou?


Antwoord 6, autoriteit 2%

Bij het zoeken naar een oplossing voor het geparametriseerde Supplier-probleem, vond ik de bovenstaande antwoorden nuttig en paste ik de suggesties toe:

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

Het wordt als volgt aangeroepen:

failedMessageSupplier(String::new, msgPrefix, customMsg);

Nog niet helemaal tevreden met de overvloedige statische functieparameter, heb ik verder gegraven en met Function.identity(), ik kwam tot het volgende resultaat:

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

Aanroep nu zonder de statische functieparameter:

failedMessageSupplier(msgPrefix, customMsg)

Sinds Function.identity()een functie van het type Objectretourneert, en dat geldt ook voor de daaropvolgende aanroep van apply(msgString), een cast naar Stringis vereist – of wat het type ook is, apply() wordt gevoed.

Deze methode maakt het mogelijk om e. G. het gebruik van meerdere parameters, dynamische stringverwerking, stringconstanten voorvoegsels, achtervoegsels enzovoort.

Het gebruik van identiteit zou in theorie ook een kleine voorsprong moeten hebben op String::new, wat altijd een nieuwe string zal creëren.

Zoals Jacob Zimmerman al aangaf, de eenvoudiger geparametriseerde vorm

Supplier<Foo> makeFooFromString(String str1, String str2) { 
    return () -> new Foo(str1, str2); 
}

is altijd mogelijk. Of dit in een context zinvol is, hangt ervan af.

Zoals ook hierboven beschreven, vereisen statische methode-referentie-aanroepen het nummer van de corresponderende methode en het type return / parameters om overeen te komen met de waarden die worden verwacht door de functie-intensieve (stream) methode.


Antwoord 7

Koppel de leverancier met een functionele interface.

Hier is wat voorbeeldcode die ik heb samengesteld om te demonstreren dat een constructorverwijzing “bindt” aan een specifieke constructor met Function en ook verschillende manieren om de “fabrieks”-constructorverwijzingen te definiëren en aan te roepen.

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

Other episodes