在Java中,方法引用的类部分可以成为函数的第一个参数。我在工作代码中看到了很多示例。但我想知道 Java 语言规范中的哪里对此进行了详细说明?我很想知道,因为每当我看到这样的代码时,我都会花几分钟的时间来弄清楚为什么有一个“额外的”第一个参数。所以我认为阅读实际规则会有所帮助。函数式接口或者方法引用的章节里好像没有提到。
这是一个例子。
import java.util.function.BiFunction;
public class TestBiFunction
{
public static void main(String[] args) {
BiFunction<A.B, Integer, Integer> fx = A.B::dbl;
A.B b = new A.B();
int result = fx.apply(b, 3);
System.out.println("Result = " + result);
}
private static class A {
private static class B {
public int dbl(int x) {
return 2 * x;
}
}
}
}
那么,我具体想知道的是,在语句“BiFunction
在 Java 语言规范中很难找到这一点,但我认为您正在寻找 第 15.13.3 节,至少在规范的第 8 版中,特别是该行
如果编译时声明是实例方法,则目标引用是调用方法的第一个形参。否则就没有目标参考。
正如@LouisWasserman的回答提到的,它是§15.13.3
一种有用的思考方式:
receiver.method(arg1, arg2);
这和:
之间没有任何有意义的区别吗?TypeOfReceiver.method(receiver, arg1, arg2);
除了动态分派(其中在编译时表达式的类型是相关的,但在运行时,您通过引用找到的实际对象,如果它是覆盖该方法的更具体类型,则覆盖的调用实现 - 这是实例方法所独有的,而不是静态方法所获得的)。
事实上,在 JVM 级别,它实际上或多或少是这样工作的,您甚至可以将它添加到方法定义中!这是合法的java:
public class MyExample {
void instanceMethod(MyExample this, int param1) {
}
}
instanceMethod
是一个带有 1 (!) 个参数 (param1) 的实例方法。您可以使用以下方式调用:new MyExample().instanceMethod(5)
。 MyExample this
完全被忽略,但合法。它的存在是为了让您可以在“接收者”上添加注释。
当考虑这个假设的实例方法时,它确实有 2 个参数。 JVM 实现将在调用之前将 2 个东西压入堆栈:
this
引用和 param1
值。
同样的想法也适用于 lambda。给定 just “嘿,我正在谈论 instanceMethod 作为一种方法”,那么它确实有 2 个参数。
Java 确实允许部分绑定的概念,不过 - 您可以选择提供一半的参数列表(具体来说,
this
参考):
MyExample myEx = new MyExample();
IntConsumer ic = myEx::instanceMethod;
ic.accept(5);
有效 - 最后一行与调用
myEx.instanceMethod(5)
相同。 myEx
引用“烘焙”到 ic
,因此这就是它的来源,而“5”是通过调用 accept
提供的。
对比:
MyExample myEx = new MyExample();
BiConsumer<MyExample, Integer> tif = MyExample::instanceMethod;
tif.accept(myEx, 5);
(没有 BiIntConsumer 原始变体,因此我们必须求助于自动装箱才能使其工作) - 这里我们的
tif
函数没有预先绑定参数,因此需要提供 both。毕竟,如果没有 instanceMethod
的实例,就不可能调用 MyExample
。那么 tif.accept(5)
将如何工作 - this
在 instanceMethod
代码的上下文中会包含什么?
因此,我的建议是 - 除了专门阅读有关lambda的规范之外,请考虑java中的每个非静态方法作为第一个参数,
TypeThisIsIn this
,即使你不写它。你可以写下,如果你愿意,编译器甚至会让你这么做。