Java 8 Supplier with arguments in the constructor

111,354

Solution 1

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 2

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 3

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

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

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?

Share:
111,354
cahen
Author by

cahen

Updated on February 22, 2020

Comments

  • cahen
    cahen about 4 years

    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"))
    
  • L. Blanc
    L. Blanc about 5 years
    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.