Java 8 Supplier with arguments in the constructor

JavaLambdaFunctional ProgrammingJava 8Functional Interface

Java Problem Overview


Why do suppliers only support no-arg constructors?

If the default constructor is present, I can do this:

create(Foo::new)

But if the only constructor takes a String, I have to do this:

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

Java Solutions


Solution 1 - Java

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.

Solution 2 - Java

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.

Solution 3 - Java

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"));

Solution 4 - Java

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.

Solution 5 - Java

> 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?

Solution 6 - Java

When looking for a solution to the parametrized Supplier problem, I found the above answers helpful and applied the suggestions:

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);
}

It is invoked like this:

failedMessageSupplier(String::new, msgPrefix, customMsg);

Not quite satisfied yet with the abundant static function parameter, I dug further and with Function.identity(), I came to the following result:

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);
}; 

Invocation now without the static function parameter:

failedMessageSupplier(msgPrefix, customMsg)

Since Function.identity() returns a function of the type Object, and so does the subsequent call of apply(msgString), a cast to String is required - or whatever the type, apply() is being fed with.

This method allows for e. g. using multiple parameters, dynamic string processing, string constants prefixes, suffixes and so on.

Using identity should theoretically also have a slight edge over String::new, which will always create a new string.

As Jacob Zimmerman already pointed out, the simpler parametrized form

Supplier<Foo> makeFooFromString(String str1, String str2) { 
    return () -> new Foo(str1, str2); 
}

is always possible. Whether or not this makes sense in a context, depends.

As also described above, static Method reference calls require the corresponding method's number and type of return / parameters to match the ones expected by the function-consuming (stream) method.

Solution 7 - Java

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);
        }
    }
}

Solution 8 - Java

If you have a constructor for new Klass(ConstructorObject) then you can use Function<ConstructorObject, Klass> like this:

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);
    }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestioncahenView Question on Stackoverflow
Solution 1 - JavaBrian GoetzView Answer on Stackoverflow
Solution 2 - JavaLouis WassermanView Answer on Stackoverflow
Solution 3 - JavaTagir ValeevView Answer on Stackoverflow
Solution 4 - JavaJacob ZimmermanView Answer on Stackoverflow
Solution 5 - Javathe8472View Answer on Stackoverflow
Solution 6 - JavafozzybearView Answer on Stackoverflow
Solution 7 - JavaNathan NiesenView Answer on Stackoverflow
Solution 8 - JavaGhilterasView Answer on Stackoverflow