Proveedor de Java 8 con argumentos en el constructor

81

¿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"))
Cahen
fuente
9
¿Cómo podría adivinar el compilador que se supone que el argumento es "hola"?
assylias
6
Tu pregunta simplemente no tiene sentido. Escribe “¿Por qué los proveedores solo funcionan con constructores sin argumentos ?”, Luego demuestra que a Supplier 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) ...
Holger

Respuestas:

62

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.

Louis Wasserman
fuente
69

Pero, un constructor de 1 argumento para Teso toma a Stringes compatible con Function<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.

Brian Goetz
fuente
46

Si le gustan tanto las referencias a métodos, puede escribir un bindmé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"));
Tagir Valeev
fuente
14

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 tipo T. 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 una Supplier<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 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 sea también () -> To sea () -> Fooespecíficamente .

Si desea usar un Supplierque pueda tomar cualquier String como argumento, debe usar algo como el método de vinculación que mencionó @Tagir, evitando la necesidad de proporcionar 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ía cambiar todas las llamadas "hacer ..." a "suministrar ..." llamadas, solo para que sea un poco más claro.

Jacob Zimmerman
fuente
12

¿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>'s R 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?

the8472
fuente
3
Esta sería una mejor respuesta si estuviera escrita con un poco más de énfasis en la comunicación. Tener tanto "isomorfo" como "interfaz SAM" en la primera oración parece excesivo para un sitio que existe para ayudar a las personas con algo que no entienden.
L. Blanc
1

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);
        }
    }
}
Nathan Niesen
fuente
1

Al buscar una solución al Supplierproblema 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:

failedMessageSupplier(msgPrefix, customMsg)

Dado que Function.identity()devuelve una función del tipo Object, y también lo hace la llamada posterior de apply(msgString), Stringse 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).

fozzybear
fuente
0

Si tiene un constructor para new Klass(ConstructorObject), puede usarlo Function<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);
    }
}
Ghilteras
fuente