¿Por qué los proveedores solo admiten constructores sin argumentos?
Si el constructor predeterminado está presente, puedo hacer esto:
create(Foo::new)
Pero si el único constructor toma una cadena, tengo que hacer esto:
create(() -> new Foo("hello"))
Supplier
sí funciona con argumentos proporcionados, es decir, cuando se usa una expresión lambda. Entonces, parece que su pregunta real es "¿por qué una referencia de método funciona solo si los parámetros funcionales coinciden con los parámetros de destino?" Y la respuesta es, porque para eso son las referencias de método. Si la lista de parámetros no coincide, use una expresión lambda como ya mostró en su pregunta. Porque para eso son las expresiones lambda (no exclusivamente) ...Respuestas:
Eso es solo una limitación de la sintaxis de referencia del método, que no puede pasar ninguno de los argumentos. Así es como funciona la sintaxis.
fuente
Pero, un constructor de 1 argumento para
T
eso toma aString
es compatible conFunction<String,T>
:Function<String, Foo> fooSupplier = Foo::new;
El constructor seleccionado se trata como un problema de selección de sobrecarga, según la forma del tipo de destino.
fuente
Si le gustan tanto las referencias a métodos, puede escribir un
bind
método usted mismo y usarlo:public static <T, R> Supplier<R> bind(Function<T,R> fn, T val) { return () -> fn.apply(val); } create(bind(Foo::new, "hello"));
fuente
La
Supplier<T>
interfaz representa una función con una firma de() -> T
, lo que significa que no toma parámetros y devuelve algo de tipoT
. Las referencias de método que proporcione como argumentos deben seguir esa firma para poder pasar.Si desea crear un
Supplier<Foo>
que funcione con el constructor, puede usar el método de vinculación general que sugiere @Tagir Valeev, o puede crear uno más especializado.Si desea un
Supplier<Foo>
que siempre use ese"hello"
String, puede definirlo de dos formas diferentes: como un método o unaSupplier<Foo>
variable.método:
static Foo makeFoo() { return new Foo("hello"); }
variable:
static Supplier<Foo> makeFoo = () -> new Foo("hello");
Puede pasar el método con una referencia de método (
create(WhateverClassItIsOn::makeFoo);
), y la variable se puede pasar simplemente usando el nombrecreate(WhateverClassItIsOn.makeFoo);
.El método es un poco más preferible porque es más fácil de usar fuera del contexto de ser pasado como referencia de método, y también se puede usar en el caso de que alguien requiera su propia interfaz funcional especializada que sea también
() -> T
o sea() -> Foo
específicamente .Si desea usar un
Supplier
que pueda tomar cualquier String como argumento, debe usar algo como el método de vinculación que mencionó @Tagir, evitando la necesidad de proporcionarFunction
:Supplier<Foo> makeFooFromString(String str) { return () -> new Foo(str); }
Puede pasar esto como un argumento como este:
create(makeFooFromString("hello"));
Aunque, tal vez debería cambiar todas las llamadas "hacer ..." a "suministrar ..." llamadas, solo para que sea un poco más claro.
fuente
Debido a que un constructor de 1 argumento es isomorfo a una interfaz SAM con 1 argumento y 1 valor de retorno, como
java.util.function.Function<T,R>
'sR apply(T)
.Por otro lado
Supplier<T>
,T get()
es isomorfo a un constructor de cero arg.Simplemente no son compatibles. O su
create()
método debe ser polimórfico para aceptar varias interfaces funcionales y actuar de manera diferente según los argumentos que se proporcionen o debe escribir un cuerpo lambda para que actúe como código de unión entre las dos firmas.¿Cuál es su expectativa insatisfecha aquí? ¿Qué debería pasar en tu opinión?
fuente
Empareje el proveedor con una interfaz funcional.
Aquí hay un código de muestra que reuní para demostrar "vincular" una referencia de constructor a un constructor específico con Function y también diferentes formas de definir e invocar las referencias de constructor de "fábrica".
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); } } }
fuente
Al buscar una solución al
Supplier
problema parametrizado , encontré útiles las respuestas anteriores y apliqué las sugerencias: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); }
Se invoca así:
failedMessageSupplier(String::new, msgPrefix, customMsg);
Aún no muy satisfecho con el abundante parámetro de función estática, investigué más y con Function.identity () , obtuve el siguiente resultado:
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); };
Invocación ahora sin el parámetro de función estática:
Dado que
Function.identity()
devuelve una función del tipoObject
, y también lo hace la llamada posterior deapply(msgString)
,String
se requiere una conversión a , o cualquiera que sea el tipo, se está alimentando apply ().Este método permite, por ejemplo, usar múltiples parámetros, procesamiento dinámico de cadenas, prefijos de constantes de cadena, sufijos, etc.
Teóricamente, el uso de la identidad también debería tener una ligera ventaja sobre String :: new, que siempre creará una nueva cadena.
Como ya señaló Jacob Zimmerman, la forma parametrizada más simple
Supplier<Foo> makeFooFromString(String str1, String str2) { return () -> new Foo(str1, str2); }
siempre es posible. Si esto tiene sentido o no en un contexto, depende.
Como también se describió anteriormente, las llamadas de referencia de métodos estáticos requieren que el número del método correspondiente y el tipo de retorno / parámetros coincidan con los esperados por el método que consume funciones (flujo).
fuente
Si tiene un constructor para
new Klass(ConstructorObject)
, puede usarloFunction<ConstructorObject, Klass>
así: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); } }
fuente