【问题标题】:BiConsumer and method reference of one parameter [duplicate]一个参数的BiConsumer和方法参考[重复]
【发布时间】:2019-09-22 06:37:33
【问题描述】:

为什么将一个参数的方法引用作为预期类型BiConsumer 的抽象方法需要两个参数的参数传递是合法的?

例子:

class Experiment {

    private String name;

    public Experiment(String name) {
        this.name = name;
    }

    public void oneParamMethod(Object o) {
        System.out.println(this.name + " and " + o);
    }

    public <T, S> void executeBiConsumer(BiConsumer<T, S> biCon, T in1, S in2) {
        biCon.accept(in1, in2);
    }

    public static void main(String[] args) {

        // notice that the name is "INSTANCE", but it won't be printed out
        Experiment exp = new Experiment("INSTANCE");

        // executeBiConsumer expects a functional of two params but is given a method 
        // reference of one param. HOW IS THIS LEGAL?
        exp.executeBiConsumer(Experiment::oneParamMethod, new Experiment("PARAM"), 999);
    }
}

输出:

PARAM and 999

让我们更改调用,使第二个参数不是Experiment 的实例,如下所示:

exp.executeBiConsumer(Experiment::oneParamMethod, new String("INVALID"), 999);

现在,它不会编译。


  1. 为什么如果第二个参数是Experiment 实例,代码编译时不会报错,否则为什么编译不出来?
  2. 为什么将只有一个参数的方法引用作为需要BiConsumer 的参数传递是有效的?

【问题讨论】:

    标签: java java-8 this method-reference functional-interface


    【解决方案1】:

    引用具有一个参数的实例方法的方法引用实际上有两个参数 - 第一个参数是隐式的 - 执行方法的实例。

    Experiment::oneParamMethod 等价于(Experiment e, Object o) -&gt; e.oneParamMethod(o)

    您传递给executeBiConsumerBiConsumer&lt;T, S&gt;BiConsumer&lt;Experiment,Object&gt;,这意味着它必须接收Experiment 的实例作为accept 方法的第一个参数。

    因此

    exp.executeBiConsumer(Experiment::oneParamMethod, new Experiment("PARAM"), 999);
    

    有效,但是

    exp.executeBiConsumer(Experiment::oneParamMethod, new String("INVALID"), 999);
    

    不是。

    这是一个相关的 JLS 参考 (15.13.1):

    其次,给定具有 n 个参数的目标函数类型,确定一组可能适用的方法:

    如果方法引用表达式的格式为 ReferenceType :: [TypeArguments] Identifier,则可能适用的方法是要搜索的类型的成员方法,它们具有适当的名称(由 Identifier 给出)、可访问性、arity(n 或 n -1) 和类型参数 arity(派生自 [TypeArguments]),如 §15.12.2.1 中所述。

    考虑了两个不同的参数 n 和 n-1,以说明这种形式可能指的是静态方法或实例方法。

    您的目标函数类型 - BiConsumer - 有 2 个参数。因此,可能适用的方法是要搜索的类型的成员方法 (Experiment),它们具有适当的名称 (oneParamMethod) 和 arity 2 或 1(即 1 或 2 个参数)。这包括您的 public void oneParamMethod(Object o) 方法。

    【讨论】:

    • 此规则是否扩展到具有超过 2 个参数的实例方法?例如,如果我有一个引用 29 个参数的实例方法的方法引用,然后我将它作为一个需要 30 个参数的函数式接口的参数传递,那么 JVM 是否仍将隐式参数作为第一个参数传递?
    • 您能提供参考吗?
    • @NikoGambt 是的。每当一个方法引用引用一个非静态方法时,第一个参数将成为该方法将在其上执行的实例。
    • @Andronicus 添加
    【解决方案2】:

    添加到Eran's answer

    方法引用有四种

    1. 引用静态方法
    2. 引用特定对象的实例方法
    3. 对特定类型的任意对象的实例方法的引用
    4. 对构造函数的引用

    您使用的属于第三类。如果我们使用 lambda 替换方法引用,我们可以更好地看到这一点。

    你正在做的是

    BiConsumer<Experiment, Integer> biCon = (experiment, someInt) -> 
                experiment.oneParamMethod(someInt)
    

    第一个参数变成了object,在它上面调用了oneParamMethod。与上述方法等效的方法参考是您使用的 - Experiment::oneParamMethod


    如果您要转换 oneParamMethod 静态,您会收到错误,因为 Class::staticMethod 的 lambda 形式会将参数按原样传递给静态方法。

    看起来像

    BiConsumer<Experiment, Integer> biCon = (experiment, someInt) -> 
            Experiment.oneParamMethod(experiment, someInt)
    

    并且您没有采用两个参数的oneParamMethod 方法。

    参考文献:

    Oracle Method reference

    Method References


    啊,我对第三种描述有问题。

    这并不像听起来那么复杂。当我们使用stream..filter..map 时,我们大部分时间都使用Class::instanceMethod。假设我们要过滤年龄超过 18 岁的 Person 对象。

    persons.stream()
           .filter(person -> person.getAge() > 18)
           .map(person -> person.getName()) //Get only name
           ...
    

    这里,person -&gt; person.getName() 可以写成Person::getName。这与第 3 类情况相同。第一个隐含参数是特定类型的任意对象getName实例方法

    希望对你有帮助

    【讨论】:

    • 啊,我对第三种描述有问题。对我来说,描述肯定没有帮助。使用数组和Arrays.sort() 给出的示例也没有准确指出第三种实际是什么。
    • 查看编辑。希望它能回答你的问题
    • 谢谢。现在我懂了。教程没有解释“任意对象”作为 implicit 第一个参数传入,这确实令人困惑。谁能只看教程中的文字就猜到了?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-10
    • 1970-01-01
    • 2018-10-01
    • 1970-01-01
    • 2014-10-20
    • 2012-10-25
    相关资源
    最近更新 更多