来自Oracle 方法参考教程:
引用特定类型的任意对象的实例方法
以下是对特定类型的任意对象的实例方法的引用示例:
String[] stringArray = { "Barbara", "James", "Mary", "John",
"Patricia", "Robert", "Michael", "Linda" };
Arrays.sort(stringArray, String::compareToIgnoreCase);
方法引用的等效 lambda 表达式String::compareToIgnoreCase
将具有形式参数 list (String a, String b)
,其中 a 和 b 是用于更好地描述此示例的任意名称。方法引用将调用方法a.compareToIgnoreCase(b)
。
但是,::
运营商的真正含义是什么?好吧,::
操作员可以这样描述(来自this SO question):
方法参考可以通过不同的方式获得,但它们的含义都是一样的:
- 静态方法 (
ClassName::methodName
)
- 特定对象的实例方法 (
instanceRef::methodName
)
- 特定对象的超级方法 (
super::methodName
)
- 特定类型的任意对象的实例方法 (
ClassName::methodName
)
- 类构造函数引用 (
ClassName::new
)
- 数组构造函数引用 (
TypeName[]::new
)
因此,这意味着方法引用String::compareToIgnoreCase
属于第二类 ( instanceRef::methodName
),这意味着它可以转换为(a, b) -> a.compareToIgnoreCase(b)
.
我相信下面的例子进一步说明了这一点。AComparator<String>
包含一个方法,该方法对两个操作数进行String
操作并返回一个int
. 这可以被伪描述为(a, b) ==> return int
(其中操作数是a
和b
)。如果您以这种方式查看,则以下所有内容都属于该类别:
// Trad anonymous inner class
// Operands: o1 and o2. Return value: int
Comparator<String> cTrad = new Comparator<String>() {
@Override
public int compare(final String o1, final String o2) {
return o1.compareToIgnoreCase(o2);
}
};
// Lambda-style
// Operands: o1 and o2. Return value: int
Comparator<String> cLambda = (o1, o2) -> o1.compareToIgnoreCase(o2);
// Method-reference à la bullet #2 above.
// The invokation can be translated to the two operands and the return value of type int.
// The first operand is the string instance, the second operand is the method-parameter to
// to the method compareToIgnoreCase and the return value is obviously an int. This means that it
// can be translated to "instanceRef::methodName".
Comparator<String> cMethodRef = String::compareToIgnoreCase;
这个很棒的 SO-answer 解释了 lambda 函数是如何编译的。在那个答案中,Jarandinor引用了 Brian Goetz 优秀文档中的以下段落,该文档描述了有关 lambda 翻译的更多信息。
我们不是生成字节码来创建实现 lambda 表达式的对象(例如调用内部类的构造函数),而是描述构造 lambda 的方法,并将实际构造委托给语言运行时。该配方编码在调用动态指令的静态和动态参数列表中。
基本上,这意味着本机运行时决定如何翻译 lambda。
布赖恩继续说:
方法引用的处理方式与 lambda 表达式相同,不同之处在于大多数方法引用不需要去糖化为新方法;我们可以简单地为引用的方法加载一个常量方法句柄并将其传递给元工厂。
因此,lambda 被脱糖成一种新的方法。例如
class A {
public void foo() {
List<String> list = ...
list.forEach( s -> { System.out.println(s); } );
}
}
上面的代码将被取消为如下所示:
class A {
public void foo() {
List<String> list = ...
list.forEach( [lambda for lambda$1 as Consumer] );
}
static void lambda$1(String s) {
System.out.println(s);
}
}
但是,Brian 在文档中也解释了这一点:
如果去糖方法是实例方法,则接收者被认为是第一个参数
Brian 继续解释lambda 的剩余参数作为参数传递给被引用的方法。
因此,在Moandji EzanacompareToIgnoreCase
的这篇文章的帮助下, as a的脱糖Comparator<String>
可以分解为以下步骤:
Collections#sort
对于 aList<String>
期望 aComparator<String>
Comparator<String>
是方法的功能接口,int sort(String, String)
相当于BiFunction<String, String, Integer>
- 因此,比较器实例可以由
BiFunction
兼容的 lambda 提供:(String a, String b) -> a.compareToIgnoreCase(b)
String::compareToIgnoreCase
指的是一个带String
参数的实例方法,所以兼容上面的lambda:String a
成为接收者,String b
成为方法参数
编辑:从 OP 输入后,我添加了一个解释脱糖的低级示例