我想知道所有这些带有方法引用和功能接口(interface)的东西如何在较低级别上工作。 最简单的例子是我们有一些列表
List<String> list = new ArrayList<>();
list.add("b");
list.add("a");
list.add("c"):
现在我们想用 Collections 类对其进行排序,所以我们可以调用:
Collections.sort(list, String::compareToIgnoreCase);
但是如果我们定义自定义比较器,它可能是这样的:
Comparator<String> customComp = new MyCustomOrderComparator<>();
Collections.sort(list, customComp::compare);
问题是 Collections.sort
有两个参数:List 和 Comparator。由于 Comparator 是功能接口(interface),它可以用具有相同签名(参数和返回类型)的 lambda 表达式或方法引用替换。那么,当方法引用 String::compareToIgnoreCase
引用仅采用一个参数的实例方法时,这些方法的签名不匹配时,传递方法引用如何工作?
Java8 中的方法引用是如何翻译的?
最佳答案
来自Oracle method references tutorial :
Reference to an Instance Method of an Arbitrary Object of a Particular Type
The following is an example of a reference to an instance method of an arbitrary object of a particular type:
String[] stringArray = { "Barbara", "James", "Mary", "John", "Patricia", "Robert", "Michael", "Linda" };
Arrays.sort(stringArray, String::compareToIgnoreCase);
The equivalent lambda expression for the method referenceString::compareToIgnoreCase
would have the formal parameter list(String a, String b)
, where a and b are arbitrary names used to better describe this example. The method reference would invoke the methoda.compareToIgnoreCase(b)
.
但是,::
是什么意思?运营商究竟是什么意思?嗯,::
运算符可以这样描述(来自 this SO question ):
Method Reference can be obtained in different styles, but they all mean the same:
- A static method (
ClassName::methodName
)- An instance method of a particular object (
instanceRef::methodName
)- A super method of a particular object (
super::methodName
)- An instance method of an arbitrary object of a particular type (
ClassName::methodName
)- A class constructor reference (
ClassName::new
)- An array constructor reference (
TypeName[]::new
)
所以,这意味着方法引用 String::compareToIgnoreCase
属于第二类( instanceRef::methodName
),这意味着它可以被翻译成 (a, b) -> a.compareToIgnoreCase(b)
.
我相信以下示例可以进一步说明这一点。 Comparator<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;
This great SO-answer to explains how lambda functions are compiled .在那个答案中 Jarandinor请引用以下来自 Brian Goetz 的优秀文档,该文档描述了 more about lambda translations .
Instead of generating bytecode to create the object that implements the lambda expression (such as calling a constructor for an inner class), we describe a recipe for constructing the lambda, and delegate the actual construction to the language runtime. That recipe is encoded in the static and dynamic argument lists of an invokedynamic instruction.
基本上这意味着 native 运行时决定如何转换 lambda。
布莱恩继续说道:
Method references are treated the same way as lambda expressions, except that most method references do not need to be desugared into a new method; we can simply load a constant method handle for the referenced method and pass that to the metafactory.
因此,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 在文档中也解释了这一点:
if the desugared method is an instance method, the receiver is considered to be the first argument
Brian 继续解释说,lambda 的剩余参数将作为参数传递给所引用的方法。
因此,在 this entry by Moandji Ezana 的帮助下,脱糖 compareToIgnoreCase
作为 Comparator<String>
可以分解为以下步骤:
-
Collections#sort
对于List<String>
期望一个Comparator<String>
-
Comparator<String>
是方法的功能接口(interface)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 输入后,我添加了一个低级别示例来解释脱糖
关于java - Java8中不同参数方法的引用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27754335/