Java 8 Supplier with arguments in the constructor
Asked Answered
I

8

110

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"))
Incline answered 6/7, 2015 at 17:3 Comment(2)
How could the compiler guess that the argument is supposed to be "hello"?Urge
Your question simply makes no sense. You write “Why do suppliers only work with no-arg constructors?”, then you prove yourself that a Supplier does work with supplied arguments, i.e. when using a lambda expression. So it seems your actual question is “why does a method reference work only if the functional parameters match the target parameters” and the answer is, because that’s what method references are for. If the parameter list doesn’t match, use a lambda expression as you have already shown in your question. Because that’s what lambda expression are for (not exclusively)…Melnick
T
71

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.

Tenet answered 6/7, 2015 at 17:23 Comment(2)
I like to differ here from this answer, although it is correct, the question meat why we can't have supplier with a arguments, like we have for consumers and functions. Also, why we don't have overloading apis in stream and collectors , which accepts supplier with an argument, so that when we create objects using supplier we can initialize the supplier object with some field from stream object. I think, the main reason probably is, where do you stop, you want one argument, then two , three. There is no end, hence they just kept it simple. No args.Moonset
@user2757415: I don't think that's true. A supplier with an argument is a Function.Tenet
S
92

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.

Sivas answered 7/7, 2015 at 0:56 Comment(0)
T
71

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.

Tenet answered 6/7, 2015 at 17:23 Comment(2)
I like to differ here from this answer, although it is correct, the question meat why we can't have supplier with a arguments, like we have for consumers and functions. Also, why we don't have overloading apis in stream and collectors , which accepts supplier with an argument, so that when we create objects using supplier we can initialize the supplier object with some field from stream object. I think, the main reason probably is, where do you stop, you want one argument, then two , three. There is no end, hence they just kept it simple. No args.Moonset
@user2757415: I don't think that's true. A supplier with an argument is a Function.Tenet
G
61

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"));
Gradualism answered 7/7, 2015 at 2:54 Comment(0)
T
22

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.

Talkie answered 8/7, 2015 at 15:37 Comment(0)
D
16

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?

Doreathadoreen answered 6/7, 2015 at 18:3 Comment(2)
This would be a better answer if it was written with a little more emphasis on communicating. Having both "isomorphic" and "SAM interface" in the first sentence seems like overkill for a site that exists to help people with something they don't understand.Britanybritches
Yeeeah... This is a better, more informative answer.Septivalent
F
2

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);
        }
    }
}
Fenian answered 9/5, 2017 at 21:12 Comment(0)
U
2

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.

Universalism answered 5/1, 2020 at 23:50 Comment(0)
A
1

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);
    }
}
Asch answered 22/2, 2020 at 1:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.