sobrecarga - Proveedor Java 8 con argumentos en el constructor
que son los parĂ¡metros o argumentos en java (6)
¿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"))
¿Por qué los proveedores solo trabajan con constructores sin argumentos?
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>
R apply(T)
.
Por otro lado, el
T get()
Supplier<T>
es isomorfo a un constructor de cero argumentos.
Simplemente no son compatibles.
O bien, su método
create()
debe ser polimórfico para aceptar varias interfaces funcionales y actuar de manera diferente dependiendo de 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 suceder en tu opinión?
Empareje el proveedor con una interfaz funcional.
Aquí hay un código de muestra que preparé 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);
}
}
}
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.
La interfaz
Supplier<T>
representa una función con una firma de
() -> T
, lo que significa que no toma parámetros y devuelve algo de tipo
T
Las referencias de método que proporcione como argumentos deben seguir esa firma para que se pasen.
Si desea crear un
Supplier<Foo>
que funcione con el constructor, puede usar el método de enlace general que sugiere @Tagir Valeev, o puede hacer uno más especializado.
Si desea un
Supplier<Foo>
que siempre use esa Cadena
"hello"
, puede definirla de dos maneras diferentes: como un método o una variable
Supplier<Foo>
.
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 nombre
create(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 también es
() -> T
or is
() -> Foo
específicamente.
Si desea usar un
Supplier
que pueda tomar cualquier Cadena como argumento, debe usar algo como el método de enlace que @Tagir mencionó, evitando la necesidad de suministrar la
Function
:
Supplier<Foo> makeFooFromString(String str) { return () -> new Foo(str); }
Puede pasar esto como un argumento como este:
create(makeFooFromString("hello"));
Aunque, tal vez deberías cambiar todas las llamadas "hacer ..." a las llamadas "suministrar ...", solo para hacerlo un poco más claro.
Pero, un constructor de 1 argumento para
T
que toma una
String
es compatible con la
Function<String,T>
:
Function<String, Foo> fooSupplier = Foo::new;
El constructor seleccionado se trata como un problema de selección de sobrecarga, en función de la forma del tipo de destino.
Si le gustan tanto las referencias de métodos, puede escribir un método de
bind
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"));