Dostawca Java 8 z argumentami w konstruktorze

82

Dlaczego dostawcy wspierają tylko konstruktorów bez argonu?

Jeśli obecny jest domyślny konstruktor, mogę to zrobić:

create(Foo::new)

Ale jeśli jedyny konstruktor przyjmuje String, muszę to zrobić:

create(() -> new Foo("hello"))
cahen
źródło
9
Jak kompilator mógł zgadnąć, że argumentem jest „cześć”?
assylias
6
Twoje pytanie po prostu nie ma sensu. Piszesz: „Dlaczego dostawcom tylko pracę bez-Arg konstruktorów?”, A następnie udowodnić sobie, że Supplier czyni pracę z dostarczonych argumentów, czyli przy użyciu wyrażenia lambda. Wydaje się więc, że Twoje aktualne pytanie brzmi: „dlaczego odniesienie do metody działa tylko wtedy, gdy parametry funkcjonalne są zgodne z parametrami docelowymi”, a odpowiedź brzmi: ponieważ do tego służą odwołania do metod. Jeśli lista parametrów nie jest zgodna, użyj wyrażenia lambda, jak już pokazałeś w swoim pytaniu. Ponieważ do tego służy wyrażenie lambda (nie tylko)…
Holger

Odpowiedzi:

62

To tylko ograniczenie składni odwołania do metody - nie możesz przekazać żadnego z argumentów. Po prostu działa składnia.

Louis Wasserman
źródło
69

Ale konstruktor 1-argowy do Ttego przyjmuje a Stringjest zgodny z Function<String,T>:

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

Wybrany konstruktor jest traktowany jako problem z wyborem przeciążenia w oparciu o kształt typu docelowego.

Brian Goetz
źródło
47

Jeśli tak bardzo lubisz odwołania do metod, możesz napisać bindmetodę samodzielnie i użyć jej:

public static <T, R> Supplier<R> bind(Function<T,R> fn, T val) {
    return () -> fn.apply(val);
}

create(bind(Foo::new, "hello"));
Tagir Valeev
źródło
14

Supplier<T>Interfejs reprezentuje funkcję z podpisem () -> T, co oznacza, że nie wymaga żadnych parametrów i zwraca coś typu T. Odwołania do metod, które podajesz jako argumenty, muszą następować po tym podpisie, aby mogły zostać przekazane.

Jeśli chcesz stworzyć coś, Supplier<Foo>co działa z konstruktorem, możesz użyć ogólnej metody bind, którą sugeruje @Tagir Valeev, lub stworzyć bardziej wyspecjalizowaną.

Jeśli chcesz, Supplier<Foo>aby zawsze używał tego ciągu "hello", możesz zdefiniować go na dwa różne sposoby: jako metodę lub Supplier<Foo>zmienną.

metoda:

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

zmienna:

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

Możesz przekazać metodę za pomocą odwołania do metody ( create(WhateverClassItIsOn::makeFoo);), a zmienną można przekazać po prostu za pomocą nazwy create(WhateverClassItIsOn.makeFoo);.

Ta metoda jest nieco bardziej preferowana, ponieważ jest łatwiejsza do użycia poza kontekstem, ponieważ jest przekazywana jako odniesienie do metody, a także może być używana w przypadku, gdy ktoś wymaga własnego wyspecjalizowanego interfejsu funkcjonalnego, który jest również () -> Tlub jest () -> Fookonkretny .

Jeśli chcesz użyć Supplierargumentu, który może przyjmować dowolny ciąg znaków jako argument, powinieneś użyć czegoś takiego jak wspomniana metoda bind @Tagir, omijając potrzebę podania Function:

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

Możesz przekazać to jako argument w następujący sposób: create(makeFooFromString("hello"));

Chociaż, może powinieneś zmienić wszystkie wywołania „wykonaj…” na „dostarczaj…”, żeby było to trochę jaśniejsze.

Jacob Zimmerman
źródło
12

Dlaczego dostawcy współpracują tylko z konstruktorami bez argonu?

Ponieważ konstruktor 1-argowy jest izomorficzny z interfejsem SAM z 1 argumentem i 1 wartością zwracaną, taką jak java.util.function.Function<T,R>'s R apply(T).

Z drugiej strony Supplier<T>„s T get()jest izomorficzna z konstruktora zerowy arg.

Po prostu nie są kompatybilne. Albo twoja create()metoda musi być polimorficzna, aby akceptować różne interfejsy funkcjonalne i działać inaczej w zależności od dostarczonych argumentów, albo musisz napisać treść lambda, która będzie działać jako kod klejący między dwoma podpisami.

Jakie jest twoje niespełnione oczekiwanie? Co Twoim zdaniem powinno się wydarzyć?

the8472
źródło
3
To byłaby lepsza odpowiedź, gdyby została napisana z nieco większym naciskiem na komunikację. Posiadanie zarówno „izomorficznego”, jak i „interfejsu SAM” w pierwszym zdaniu wydaje się przesadą w przypadku witryny, która istnieje po to, aby pomóc ludziom w czymś, czego nie rozumieją.
L. Blanc
1

Sparuj dostawcę z FunctionalInterface.

Oto przykładowy kod, który utworzyłem, aby zademonstrować „wiązanie” odniesienia konstruktora do określonego konstruktora za pomocą funkcji Function, a także różne sposoby definiowania i wywoływania odniesień do konstruktora „fabrycznego”.

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);
        }
    }
}
Nathan Niesen
źródło
1

Szukając rozwiązania sparametryzowanego Supplier problemu, powyższe odpowiedzi okazały się pomocne i zastosowałem sugestie:

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

Jest wywoływany w ten sposób:

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

Nie całkiem zadowolony z obfitego parametru funkcji statycznej, sięgnąłem dalej i za pomocą funkcji Function.identity () doszedłem do następującego wyniku:

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

Wywołanie teraz bez statycznego parametru funkcji:

failedMessageSupplier(msgPrefix, customMsg)

Ponieważ Function.identity()zwraca funkcję typuObject , podobnie jak późniejsze wywołanie funkcji apply(msgString), Stringwymagane jest rzutowanie na - lub jakikolwiek typ, który jest podawany za pomocą metody apply ().

Metoda ta pozwala np. Na użycie wielu parametrów, dynamiczne przetwarzanie napisów, przedrostki stałych łańcuchowych, sufiksy i tak dalej.

Używanie tożsamości powinno teoretycznie mieć również niewielką przewagę nad String :: new, który zawsze tworzy nowy ciąg.

Jak już zauważył Jacob Zimmerman, prostsza sparametryzowana forma

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

jest zawsze możliwe. To, czy ma to sens w kontekście, zależy, czy nie.

Jak również opisano powyżej, wywołania odwołania do metody statycznej wymagają, aby numer odpowiedniej metody i typ zwracanych parametrów / parametrów odpowiadał oczekiwaniom metody pochłaniającej funkcję (strumień).

fozzybear
źródło
0

Jeśli masz konstruktora new Klass(ConstructorObject), możesz użyć Function<ConstructorObject, Klass>tego:

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);
    }
}
Ghilteras
źródło