Java 8 Supplier with arguments in the constructor

依然范特西╮ 提交于 2019-11-27 19:13:51

That's just a limitation of the method reference syntax -- that you can't pass in any of the arguments. It's just how the syntax works.

Brian Goetz

But, a 1-arg constructor for T that takes a String is compatible with Function<String,T>:

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

Which constructor is selected is treated as an overload selection problem, based on the shape of the target type.

If you like method references so much, you can write a bind method by yourself and use it:

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

create(bind(Foo::new, "hello"));

Why do suppliers only work with no-arg constructors?

Because a 1-arg constructor is isomorphic to a SAM interface with 1 argument and 1 return value, such as java.util.function.Function<T,R>'s R apply(T).

On the other hand Supplier<T>'s T get() is isomorphic to a zero arg constructor.

They are simply not compatible. Either your create() method needs to be polymorphic to accept various functional interfaces and act differently depending on which arguments are supplied or you have to write a lambda body to act as glue code between the two signatures.

What is your unmet expectation here? What should happen in your opinion?

The Supplier<T> interface represents a function with a signature of () -> T, meaning it takes no parameters and returns something of type T. Method references that you provide as arguments must follow that signature in order to be passed in.

If you want to create a Supplier<Foo> that works with the constructor, you can use the general bind method that @Tagir Valeev suggests, or you make a more specialized one.

If you want a Supplier<Foo> that always uses that "hello" String, you could define it one of two different ways: as a method or a Supplier<Foo> variable.

method:

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

variable:

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

You can pass in the method with a method reference(create(WhateverClassItIsOn::makeFoo);), and the variable can be passed in simply using the name create(WhateverClassItIsOn.makeFoo);.

The method is a little bit more preferable because it is easier to use outside of the context of being passed as a method reference, and it's also able to be used in the instance that someone requires their own specialized functional interface that is also () -> T or is () -> Foo specifically.

If you want to use a Supplier that can take any String as an argument, you should use something like the bind method @Tagir mentioned, bypassing the need to supply the Function:

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

You can pass this as an argument like this: create(makeFooFromString("hello"));

Although, maybe you should change all the "make..." calls to "supply..." calls, just to make it a little clearer.

Pair the Supplier with a FunctionalInterface.

Here's some sample code I put together to demonstrate "binding" a constructor reference to a specific constructor with Function and also different ways of defining and invoking the "factory" constructor references.

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);
        }
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!