在构造函数中带有参数的 Java8供应商

为什么供应商只支持无参构造函数?

如果缺省构造函数在场,我可以这样做:

create(Foo::new)

但是如果唯一的构造函数使用 String,我必须这样做:

create(() -> new Foo("hello"))
125310 次浏览

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.

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?

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

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

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.

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