示例代码:
class Outer {
public Integer i;
Outer(Integer i) {
this.i = i;
}
public int getVal() { return i; }
}
class MyClass {
public Integer f(Outer o) { return o.getVal();};
public void main() {
MyClass g = new MyClass();
List<Integer> l1 = Arrays.asList(new Outer(2)).stream().map(g::f).collect(Collectors.toList());
List<Integer> l2 = Arrays.asList(new Outer(2)).stream().map(Outer::getVal).collect(Collectors.toList());
}
}
使用
的任一方法参考 Outer::instanceMethod
不带参数,基本上是Supplier<T>
功能界面。 [1]
MyClass::instanceMethod
接受Outer
类型的参数,并且是Function<T,R>
功能接口。 [1]
有效。那么map
函数如何知道将选项(1)中的函数应用于流的对象,但是将流对象传递给选项(2)中的函数?
[1] https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html
答案 0 :(得分:4)
首先,map
方法不知道如何处理方法引用。这是编译器的工作。在这两种情况下,map
都需要:
Function<? super PackageName.outer,? extends Integer>
对于您的特定问题,根据docs的两个方法引用都是对特定对象的实例方法的引用
关于编译器如何处理lambdas和方法引用并将它们转换为字节码this document,强烈建议阅读。与您的问题最相关的部分(强调我的总结):
当编译器遇到lambda表达式时,它会先降低 (desugars)将lambda体转换为一个方法,其参数列表和 返回类型匹配lambda表达式的表达式,可能与某些表达式匹配 附加参数(对于从词法范围捕获的值,如果 任何。)在捕获lambda表达式的时刻, 它会生成一个invokedynamic调用站点,在调用时会返回该站点 lambda所在的功能接口的一个实例 转换即可。对于给定,此调用站点称为lambda factory 拉姆达。 lambda工厂的动态参数是值 从词汇范围中捕获。 lambda的bootstrap方法 factory是 Java语言运行时库中的标准化方法, 称为lambda metafactory 。静态引导参数捕获 关于lambda在编译时已知的信息(功能性的 它将被转换的接口,一个方法句柄 desugared lambda body,有关SAM类型的信息 可序列化等。)
方法引用的处理方式与lambda表达式相同, 除了大多数方法参考不需要被贬低为a 新方法;我们可以简单地加载一个常量方法句柄 引用的方法并将其传递给metafactory
实例捕获方法参考表单包含绑定实例 方法引用(s :: length,用引用类型捕获 invokeVirtual 强>)
您的2个案例的字节码是:
outer::instanceMethod
// handle kind 0x5 : INVOKEVIRTUAL
PackageName/outer.getVal()I,
(LPackageName/outer;)Ljava/lang/Integer;
MyClass::instanceMethod
// handle kind 0x5 : INVOKEVIRTUAL
PackageName/MyClass.f(LPackageName/outer;)Ljava/lang/Integer;,
(LPackageName/outer;)Ljava/lang/Integer;
注意,尽管第二行在第二种情况下更复杂,但最后一行是相同的。在这两种情况下,编译器只会看到一个带outer
并返回Integer
的函数。这符合map
期望的内容。
方法参考在语言规范15.13 Method Reference Expressions中描述。在15.13.3 Run-Time Evaluation of Method References中提到了方法引用的目标引用是该方法的隐式第一个参数的事实。
如果编译时声明是实例方法,那么目标 reference是调用方法的第一个形式参数。 否则,没有目标参考