Java方法参考解析

时间:2018-04-13 13:20:47

标签: java java-8 method-reference

我正在尝试理解方法引用在java中的工作原理。 乍一看,它非常简单。但是当谈到这样的事情时却不是这样:

Foo类中有一个方法:

public class Foo {
    public Foo merge(Foo another) {
        //some logic
    }
}

在另一个类Bar中有一个这样的方法:

public class Bar {
    public void function(BiFunction<Foo, Foo, Foo> biFunction) {
       //some logic
    }
}

使用方法参考:

new Bar().function(Foo::merge);

它符合并且有效,但我不明白它是如何匹配的:

Foo merge(Foo another)

到BiFunction方法:

R apply(T t, U u);

???

2 个答案:

答案 0 :(得分:3)

实例方法有一个隐式this参数。这定义为§3.7 of the JVM specification

  

通过首先将对当前实例的引用推送到操作数堆栈来设置调用。然后推送方法调用的参数,即int值12和13。创建addTwo方法的框架时,传递给方法的参数将成为新框架局部变量的初始值。也就是说,由调用者推送到操作数堆栈的this和两个参数的引用将成为调用方法的局部变量0,1和2的初始值。

要理解为什么以这种方式完成方法调用,我们需要了解JVM如何将代码存储在内存中。对象的代码和数据是分开的。实际上,一个类(静态和非静态)的所有方法都存储在同一个地方method area (§2.5.4 of JVM spec)。这允许仅存储每个方法一次,而不是一遍又一遍地为每个类的实例重新存储它们。当像

这样的方法
someObject.doSomethingWith(someOtherObject);

被调用,它实际上被编译成看起来更像

的东西
doSomething(someObject, someOtherObject);

大多数Java程序员都同意someObject.doSomethingWith(someOtherObject)具有“较低的认知复杂度”:我们对someObject执行涉及someOtherObject的操作。此操作的中心是someObject,其中someOtherObject只是达到目的的手段。

使用doSomethingWith(someObject, someOtherObject),您不会传输someObject作为操作中心的语义。

所以从本质上讲,我们编写第一个版本,但计算机更喜欢第二个版本。

正如@FedericoPeraltaSchaffner所指出的,你甚至可以自Java 8以来明确地编写隐式this参数。确切的定义在JLS, §8.4.1中给出:

  

receiver参数是实例方法或内部类构造函数的可选语法设备。对于实例方法,receiver参数表示调用该方法的对象。对于内部类的构造函数,receiver参数表示新构造的对象的直接封闭实例。无论哪种方式,接收器参数仅存在以允许在源代码中表示所表示的对象的类型,以便可以注释该类型。接收器参数不是形式参数;更确切地说,它不是任何类型的变量的声明(§4.12.3),它永远不会绑定到在方法调用表达式或限定的类实例创建表达式中作为参数传递的任何值,并且它在任何情况下都没有任何影响运行时间。

receiver参数必须是类的类型,并且必须命名为this

这意味着

public String doSomethingWith(SomeOtherClass other) { ... }

public String doSomethingWith(SomeClass this, SomeOtherClass other) { ... }

具有相同的语义含义,但后者允许例如注释

答案 1 :(得分:2)

我发现使用不同类型更容易理解:

public class A {

    public void test(){
        function(A::merge);
    }

    public void function(BiFunction<A, B, C> f){

    }

    public C merge(B i){
        return null;
    }

    class B{}
    class C{}
}

我们可以知道,使用方法引用Test::merge而不是实例上的引用将隐式使用this作为第一个值。

15.13.3. Run-Time Evaluation of Method References

  

如果表单是ReferenceType :: [TypeArguments]标识符
  [...]
  如果编译时声明是实例方法,则目标引用是调用方法的第一个形式参数。否则,没有目标参考。

我们可以在以下主题上找到一些使用此行为的示例:
JLS - 15.13.1. Compile-Time Declaration of a Method Reference提到:

  

ReferenceType::[TypeArguments] Identifier形式的方法参考表达式可以用不同的方式解释     - 如果Identifier引用实例方法,则隐式lambda表达式具有额外参数[...]
    - 如果Identifier引用静态方法。 ReferenceType可能有两种适用的方法,因此上面描述的搜索算法分别识别它们,因为每种情况都有不同的参数类型。

然后显示这种行为可能存在一些歧义:

class C {
    int size() { return 0; }
    static int size(Object arg) { return 0; }

    void test() {
        Fun<C, Integer> f1 = C::size;
          // Error: instance method size() 
          // or static method size(Object)?
    }
}