web-dev-qa-db-fra.com

Java 8 Fournisseur avec des arguments dans le constructeur

Pourquoi les fournisseurs ne prennent-ils en charge que les constructeurs sans argument?

Si le constructeur par défaut est présent, je peux le faire:

create(Foo::new)

Mais si le seul constructeur prend une chaîne, je dois le faire:

create(() -> new Foo("hello"))
59
cahen

C'est juste une limitation de la syntaxe de référence de la méthode - que vous ne pouvez transmettre aucun des arguments. C'est juste comment fonctionne la syntaxe.

51
Louis Wasserman

Mais un constructeur à 1 argument pour T qui prend un String est compatible avec Function<String,T>:

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

Le constructeur sélectionné est traité comme un problème de sélection de surcharge basé sur la forme du type de cible.

50
Brian Goetz

Si vous aimez tant les références de méthodes, vous pouvez écrire une méthode bind par vous-même et l'utiliser:

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

create(bind(Foo::new, "hello"));
36
Tagir Valeev

Pourquoi les fournisseurs travaillent-ils uniquement avec des constructeurs sans argument?

Parce qu'un constructeur à 1 argument est isomorphe à une interface SAM avec 1 argument et 1 valeur de retour, tel que R apply(T) de Java.util.function.Function<T,R>.

D'autre part, la fonction T get() de Supplier<T> Est isomorphe à un constructeur zéro arg.

Ils ne sont tout simplement pas compatibles. Soit votre méthode create() doit être polymorphe pour accepter diverses interfaces fonctionnelles et agir différemment en fonction des arguments fournis, soit vous devez écrire un corps lambda agissant comme code collant entre les deux signatures.

Quelles sont vos attentes non satisfaites ici? Qu'est-ce que devrait arriver à votre avis?

12
the8472

L'interface Supplier<T> Représente une fonction avec une signature de () -> T, Ce qui signifie qu'elle ne prend aucun paramètre et renvoie quelque chose de type T. Les références de méthode que vous fournissez comme arguments doivent suivre cette signature pour pouvoir être passées.

Si vous voulez créer un Supplier<Foo> Qui fonctionne avec le constructeur, vous pouvez utiliser la méthode de liaison générale suggérée par @Tagir Valeev ou en créer une plus spécialisée.

Si vous voulez un Supplier<Foo> Qui utilise toujours cette chaîne "hello", Vous pouvez le définir de deux manières différentes: en tant que méthode ou comme variable Supplier<Foo>.

méthode:

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

variable:

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

Vous pouvez transmettre la méthode avec une référence de méthode (create(WhateverClassItIsOn::makeFoo);) et la variable peut être transmise simplement en utilisant le nom create(WhateverClassItIsOn.makeFoo);.

La méthode est un peu plus préférable car elle est plus facile à utiliser en dehors du contexte où elle est passée en tant que référence de méthode et elle peut également être utilisée dans le cas où une personne a besoin de sa propre interface fonctionnelle spécialisée qui est aussi () -> T Ou est spécifiquement () -> Foo.

Si vous voulez utiliser un Supplier pouvant prendre n'importe quelle chaîne comme argument, vous devriez utiliser quelque chose comme la méthode de liaison que @Tagir a mentionnée, en évitant la nécessité de fournir le Function:

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

Vous pouvez passer ceci comme un argument comme celui-ci: create(makeFooFromString("hello"));

Bien que, peut-être devriez-vous changer tous les appels "make ..." en "supply ...", juste pour que ce soit un peu plus clair.

9
Jacob Zimmerman

Associez le fournisseur à une interface fonctionnelle.

Voici un exemple de code que j'ai rassemblé pour illustrer la "liaison" d'une référence de constructeur à un constructeur spécifique avec Function, ainsi que différentes manières de définir et d'appeler les références de constructeur "fabrique".

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