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"))
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"))
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)…
Odpowiedzi:
To tylko ograniczenie składni odwołania do metody - nie możesz przekazać żadnego z argumentów. Po prostu działa składnia.
Ale konstruktor 1-argowy do T
tego przyjmuje a String
jest 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.
Jeśli tak bardzo lubisz odwołania do metod, możesz napisać bind
metodę 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"));
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ż () -> T
lub jest () -> Foo
konkretny .
Jeśli chcesz użyć Supplier
argumentu, 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.
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ć?
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);
}
}
}
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)
, String
wymagane 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ń).
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);
}
}