为什么供应商只支持无参数构造函数?
如果存在默认构造函数,我可以这样做:
create(Foo::new)
但是如果唯一的构造函数需要一个字符串,我必须这样做:
create(() -> new Foo("hello"))
为什么供应商只支持无参数构造函数?
如果存在默认构造函数,我可以这样做:
create(Foo::new)
但是如果唯一的构造函数需要一个字符串,我必须这样做:
create(() -> new Foo("hello"))
但是,采用 a 的 1-arg 构造T
函数String
与Function<String,T>
:
Function<String, Foo> fooSupplier = Foo::new;
根据目标类型的形状,选择哪个构造函数被视为重载选择问题。
这只是方法引用语法的一个限制——您不能传入任何参数。这就是语法的工作方式。
如果你非常喜欢方法引用,你可以bind
自己写一个方法并使用它:
public static <T, R> Supplier<R> bind(Function<T,R> fn, T val) {
return () -> fn.apply(val);
}
create(bind(Foo::new, "hello"));
该Supplier<T>
接口表示一个带有 签名的函数() -> T
,这意味着它不接受任何参数并返回一些类型的东西T
。您作为参数提供的方法引用必须遵循该签名才能传入。
如果您想创建一个Supplier<Foo>
与构造函数一起使用的,您可以使用@Tagir Valeev 建议的通用绑定方法,或者您制作一个更专业的方法。
如果您想要Supplier<Foo>
始终使用该"hello"
字符串的 a,您可以将其定义为两种不同的方式之一:作为方法或Supplier<Foo>
变量。
方法:
static Foo makeFoo() { return new Foo("hello"); }
多变的:
static Supplier<Foo> makeFoo = () -> new Foo("hello");
您可以使用方法引用(create(WhateverClassItIsOn::makeFoo);
)传入方法,并且可以简单地使用名称传入变量create(WhateverClassItIsOn.makeFoo);
。
该方法更可取一点,因为它更容易在作为方法引用传递的上下文之外使用,并且它也可以在某人需要自己的专用功能接口的情况() -> T
下() -> Foo
使用.
如果你想使用Supplier
可以接受任何字符串作为参数的 a,你应该使用类似 @Tagir 提到的绑定方法,绕过提供的需要Function
:
Supplier<Foo> makeFooFromString(String str) { return () -> new Foo(str); }
您可以将其作为参数传递,如下所示:create(makeFooFromString("hello"));
虽然,也许您应该将所有“make...”调用更改为“supply...”调用,只是为了让它更清晰一点。
为什么供应商只使用无参数构造函数?
因为 1-arg 构造函数与具有 1 个参数和 1 个返回值的 SAM 接口同构,例如java.util.function.Function<T,R>
's R apply(T)
。
另一方面Supplier<T>
'sT get()
与零参数构造函数同构。
它们根本不兼容。您的create()
方法需要是多态的以接受各种功能接口并根据提供的参数采取不同的行为,或者您必须编写一个 lambda 主体来充当两个签名之间的粘合代码。
您在这里有什么未满足的期望?在你看来应该发生什么?
在寻找参数化Supplier
问题的解决方案时,我发现上述答案很有帮助并应用了以下建议:
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);
}
它是这样调用的:
failedMessageSupplier(String::new, msgPrefix, customMsg);
对丰富的静态函数参数还不太满意,我进一步挖掘并使用Function.identity(),得出以下结果:
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);
};
现在调用没有静态函数参数:
failedMessageSupplier(msgPrefix, customMsg)
由于Function.identity()
返回类型的函数,Object
后续调用也是如此,因此需要apply(msgString)
强制转换String
- 或任何类型, apply() 都被提供。
该方法允许例如使用多个参数、动态字符串处理、字符串常量前缀、后缀等。
理论上,使用身份也应该比 String::new 稍有优势,后者总是会创建一个新字符串。
正如 Jacob Zimmerman 已经指出的那样,更简单的参数化形式
Supplier<Foo> makeFooFromString(String str1, String str2) {
return () -> new Foo(str1, str2);
}
总是可能的。这在上下文中是否有意义,取决于。
同样如上所述,静态方法引用调用需要相应方法的返回/参数的数量和类型与函数消耗(流)方法所期望的相匹配。
将供应商与功能接口配对。
这是我放在一起的一些示例代码,用于演示使用 Function 将构造函数引用“绑定”到特定构造函数,以及定义和调用“工厂”构造函数引用的不同方法。
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);
}
}
}
如果你有一个构造函数,new Klass(ConstructorObject)
那么你可以Function<ConstructorObject, Klass>
像这样使用:
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);
}
}