一个参数的BiConsumer和方法参考

时间:2019-09-22 06:37:33

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

为什么将一个参数的方法引用作为预期类型为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的参数是有效的?

2 个答案:

答案 0 :(得分:6)

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

Experiment::oneParamMethod等效于(Experiment e, Object o) -> e.oneParamMethod(o)

您要传递给BiConsumer<T, S>的{​​{1}}是executeBiConsumer,这意味着它必须接收BiConsumer<Experiment,Object>的实例作为Experiment的第一个参数方法。

因此

accept

有效,但是

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

不是。

以下是相关的JLS参考(15.13.1):

  

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

     

如果方法引用表达式的形式为ReferenceType :: :: [TypeArguments] Identifier,则可能适用的方法是具有适当名称(由Identifier赋予),可访问性,友善性(n或n)的要搜索类型的成员方法。 -1),然后键入参数arity(源自[TypeArguments]),如§15.12.2.1中所述。

     

考虑了两个不同的变量n和n-1,以说明此形式引用静态方法还是实例方法的可能性。

您的目标函数类型-exp.executeBiConsumer(Experiment::oneParamMethod, new String("INVALID"), 999); -有2个参数。因此,可能适用的方法是具有适当名称(BiConsumer)和Arity 2或1(即1或2个自变量)的搜索类型(Experiment)的成员方法。这包括您的oneParamMethod方法。

答案 1 :(得分:4)

添加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形式是将参数直接传递给static方法。

看起来像

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

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

参考:https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html


  

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

这听起来并不复杂。当我们使用Class::instanceMethod时,大多数时候我们都会使用stream..filter..map。假设我们要过滤年龄超过18岁的Person对象。

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

在这里,person -> person.getName()可以写为Person::getName。这与第三类相同。第一个隐式参数是特定类型的任意对象,而getName instance方法

希望这会有所帮助