Java 8构造函数带参数的供应者(Supplier)

110

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

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

create(Foo::new)

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

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

10
编译器是如何猜测这个参数应该是“hello”? - assylias
8
你的问题根本没有意义。你写道“为什么供应商只与无参构造函数一起使用?”,但你证明了一个Supplier确实可以使用提供的参数,即当使用lambda表达式时。所以你的实际问题似乎是“为什么方法引用仅在函数参数与目标参数匹配时才有效”,答案是,因为这就是方法引用的作用。如果参数列表不匹配,请使用lambda表达式,就像你已经在你的问题中展示的那样。因为这就是lambda表达式的作用(不是唯一的作用)... - Holger
8个回答

91
但是,一个接受字符串参数的T类1个参数的构造函数与Function<String,T>兼容:
Function<String, Foo> fooSupplier = Foo::new;

根据目标类型的形状,选择哪个构造函数被视为重载选择问题。

70

这只是方法引用语法的限制,即您无法传递任何参数。 这就是语法的工作方式。


我想在这里与这个答案不同,尽管它是正确的,问题是为什么我们不能像消费者和函数一样拥有带参数的供应商。此外,为什么在流和收集器中没有重载API,它接受带参数的供应商,以便当我们使用供应商创建对象时,我们可以使用流对象中的某些字段初始化供应商对象。我认为,主要原因可能是,你想要一个参数,然后两个,三个。没有止境,因此他们只是保持简单。没有参数。 - user2757415
1
@user2757415:我不认为那是真的。带有参数的供应商确实是一个“函数”。 - Louis Wasserman

61
如果您非常喜欢方法引用,您可以自己编写一个“bind”方法并使用它:

bind方法的具体实现请自行完成。

public static <T, R> Supplier<R> bind(Function<T,R> fn, T val) {
    return () -> fn.apply(val);
}

create(bind(Foo::new, "hello"));

22
Supplier<Foo>接口表示一个带有() -> T签名的函数,这意味着它不需要任何参数并返回类型为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…”调用,以使其更清晰一些。

16
为什么供应商只使用无参数构造函数?
因为1个参数的构造函数等同于一个带有1个参数和1个返回值的SAM接口,例如java.util.function.Function 的R apply(T)。
另一方面,Supplier 的T get()等同于零参数构造函数。
它们根本不兼容。 要么您的create()方法需要是多态的,以接受各种功能接口并根据提供的参数而具有不同行为,要么您必须编写一个lambda主体作为两个签名之间的粘合代码。
你的期望是什么? 在你的意见中应该发生什么?

13
如果这个回答更多地注重传达信息会更好。在第一句中同时使用“同构”和“SAM接口”似乎有些过头了,因为这个网站的存在是为了帮助那些不理解某些东西的人们。 - L. Blanc
耶...这是一个更好的、更具信息性的答案。 - Dark Star1

2

当寻找解决参数化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()被输入的类型是什么。

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

使用identity理论上也比String::new稍微快一些,因为它总是会创建一个新的字符串。

正如Jacob Zimmerman所指出的,简单的参数化形式

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

总是有可能的。但这是否在某个背景下有意义,则取决于情境。

正如上面所述,静态方法引用调用需要相应方法的返回/参数数量和类型与函数消费(流)方法所期望的匹配。


2

将供应商与FunctionalInterface配对。

这里是我编写的一些示例代码,用于演示使用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);
        }
    }
}

1
如果您有一个用于 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);
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接