Fournisseur Java 8 avec des arguments dans le constructeur

Pourquoi les fournisseurs ne prennent-ils en charge que les constructeurs no-arg?

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 faire ceci:

create(() -> new Foo("hello"))
44
demandé sur cahen 2015-07-06 20:03:24

6 réponses

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

43
répondu Louis Wasserman 2015-07-06 17:23:15

, Mais, une 1-arg constructeur de T, ce 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 cible.

42
répondu Brian Goetz 2015-12-08 15:08:12

Si vous aimez tellement les références de méthode, 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"));
32
répondu Tagir Valeev 2015-07-07 02:54:36

Pourquoi les fournisseurs ne fonctionnent - ils qu'avec des constructeurs sans arg?

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

D'autre part Supplier<T>'s T get() est isomorphe à un zéro arg constructeur.

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

Quelle est votre attente non satisfaite ici? Qu'est-ce que devrait arriver à votre avis?

10
répondu the8472 2015-07-06 18:27:30

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 être transmises.

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

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

Méthode:

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

Variable:

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

Vous pouvez passer la méthode avec une référence de méthode (create(WhateverClassItIsOn::makeFoo);), et la variable peut être passée 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 d'être passée comme référence de méthode, et elle peut également être utilisée dans l'instance que quelqu'un a besoin de la sienne interface fonctionnelle spécialisée qui est également () -> T ou est () -> Foo spécifiquement.

Si vous voulez utiliser un {[14] } qui peut prendre N'importe quelle chaîne comme argument, vous devriez utiliser quelque chose comme la méthode bind @ Tagir mentionnée, sans passer par la nécessité de fournir le Function:

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

, Vous pouvez la passer en argument comme ceci: create(makeFooFromString("hello"));

Bien que, peut-être que vous devriez changer tous les " make..."appels à" fournir..."appelle, juste pour le rendre un peu plus clair.

7
répondu Jacob Zimmerman 2015-07-08 15:37:31

Associez le fournisseur à une interface Fonctionnelleinterface.

Voici un exemple de code que j'ai mis en place pour démontrer "lier" une référence de constructeur à un constructeur spécifique avec une fonction et aussi différentes façons de définir et d'invoquer les références de constructeur "factory".

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
répondu NateN 2017-07-07 12:58:11