Java 8 供应商在构造函数中有参数

Java 8 Supplier with arguments in the constructor

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

如果存在默认构造函数,我可以这样做:

create(Foo::new)

但是如果唯一的构造函数接受一个字符串,我必须这样做:

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

这只是方法引用语法的一个限制——您不能传入任何参数。这就是语法的工作原理。

Why do suppliers only work with no-arg constructors?

因为 1-arg 构造函数同构于具有 1 个参数和 1 个 return 值的 SAM 接口,例如 java.util.function.Function<T,R>R apply(T)

另一方面,Supplier<T>T get() 与零参数构造函数同构。

它们根本不兼容。您的 create() 方法需要是多态的以接受各种功能接口并根据提供的参数采取不同的行为,或者您必须编写一个 lambda 主体作为两个签名之间的胶水代码。

您在这里未满足的期望是什么?在您看来应该发生什么?

但是,采用 StringT 的 1-arg 构造函数与 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 签名的函数,这意味着它不接受任何参数和 returns 类型 T 的东西。您作为参数提供的方法引用必须遵循该签名才能传入。

如果你想创建一个与构造函数一起工作的Supplier<Foo>,你可以使用@Tagir Valeev 建议的通用绑定方法,或者你做一个更专业的方法。

如果您想要一个始终使用该 "hello" 字符串的 Supplier<Foo>,您可以通过两种不同的方式之一定义它:作为方法或 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,你应该使用类似 @Tagir 提到的绑定方法,绕过提供 Function 的需要:

Supplier<Foo> makeFooFromString(String str) { return () -> new Foo(str); }

您可以像这样将其作为参数传递:create(makeFooFromString("hello"));

尽管如此,也许您应该将所有 "make..." 调用更改为 "supply..." 调用,只是为了让它更清晰一些。

将 Supplier 与 FunctionalInterface 配对。

这是我整理的一些示例代码,用于演示 "binding" 对具有 Function 的特定构造函数的构造函数引用,以及定义和调用 "factory" 构造函数引用的不同方式。

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

在寻找参数化 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() return 是 Object 类型的函数,apply(msgString) 的后续调用也是如此,因此需要转换为 String - 或者任何类型的 apply() 正在被喂养。

此方法允许 e. G。使用多参数、动态字符串处理、字符串常量前缀、后缀等。

使用身份在理论上也应该比 String::new 略有优势,后者总是会创建一个新字符串。

正如 Jacob Zimmerman 已经指出的,更简单的参数化形式

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

总是有可能的。这在上下文中是否有意义取决于。

同样如上所述,静态方法引用调用需要相应方法的数量和类型 return / 参数与函数消耗(流)方法所期望的相匹配。

如果您有 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);
    }
}