在Java8中引用具有不同参数的方法
我想知道所有这些方法引用和function接口如何在较低级别上工作。 最简单的例子是我们有一些List
List list = new ArrayList(); list.add("b"); list.add("a"); list.add("c"):
现在我们想用Collections类对它进行排序,所以我们可以调用:
Collections.sort(list, String::compareToIgnoreCase);
但是,如果我们定义自定义比较器,它可能是这样的:
Comparator customComp = new MyCustomOrderComparator(); Collections.sort(list, customComp::compare);
问题是Collections.sort有两个参数:List和Comparator。 由于Comparator是function接口,因此可以使用具有相同签名(参数和返回类型)的lambda表达式或方法引用替换它。 那么它如何工作我们可以传递也引用compareTo只接受一个参数并且这些方法的签名不匹配? 如何在Java8中翻译方法引用?
从Oracle方法参考教程 :
对特定类型的任意对象的实例方法的引用
以下是对特定类型的任意对象的实例方法的引用示例:
String[] stringArray = { "Barbara", "James", "Mary", "John", "Patricia", "Robert", "Michael", "Linda" };
Arrays.sort(stringArray, String::compareToIgnoreCase);
方法引用
String::compareToIgnoreCase
的等效lambda表达式将具有forms参数列表(String a, String b)
,其中a和b是用于更好地描述此示例的任意名称。 方法引用将调用方法a.compareToIgnoreCase(b)
。
但是, ::
运营商的真正含义是什么? 那么, ::
运算符可以这样描述(从这个SO问题 ):
方法参考可以以不同的样式获得,但它们都意味着相同:
- 静态方法(
ClassName::methodName
)- 特定对象的实例方法(
instanceRef::methodName
)- 特定对象的超级方法(
super::methodName
)- 特定类型的任意对象的实例方法(
ClassName::methodName
)- 类构造函数引用(
ClassName::new
)- 数组构造函数引用(
TypeName[]::new
)
因此,这意味着方法引用String::compareToIgnoreCase
属于第二类( instanceRef::methodName
),这意味着它可以转换为(a, b) -> a.compareToIgnoreCase(b)
。
我相信以下示例进一步说明了这一点。 Comparator
包含一个对两个String
操作数进行操作并返回int
。 这可以伪描述为(a, b) ==> return int
(其中操作数是a
和b
)。 如果您以这种方式查看,则以下所有属于该类别:
// Trad anonymous inner class // Operands: o1 and o2. Return value: int Comparator cTrad = new Comparator () { @Override public int compare(final String o1, final String o2) { return o1.compareToIgnoreCase(o2); } }; // Lambda-style // Operands: o1 and o2. Return value: int Comparator 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 cMethodRef = String::compareToIgnoreCase;
这个伟大的SO答案解释了如何编译lambda函数 。 在那个答案中, Jarandinor提到了Brian Goetz的以下段落,该文档描述了有关lambda翻译的更多内容 。
我们不是生成字节码来创建实现lambda表达式的对象(例如调用内部类的构造函数),而是描述构造lambda的配方,并将实际构造委托给语言运行库。 该配方在invokedynamic指令的静态和动态参数列表中进行编码。
基本上这意味着本机运行时决定如何转换lambda。
Brian继续说道:
方法引用的处理方式与lambda表达式相同,只是大多数方法引用不需要被置于新方法中; 我们可以简单地为引用的方法加载一个常量方法句柄并将其传递给metafactory。
所以,lambdas被贬低为一种新方法 。 例如
class A { public void foo() { List list = ... list.forEach( s -> { System.out.println(s); } ); } }
上面的代码将被解雇为这样的东西:
class A { public void foo() { List list = ... list.forEach( [lambda for lambda$1 as Consumer] ); } static void lambda$1(String s) { System.out.println(s); } }
但是,Brian也在文档中解释了这一点:
如果desugared方法是实例方法,则接收器被认为是第一个参数
Brian继续解释lambda的剩余参数作为参数传递给被引用的方法 。
因此,在Moandji Ezana的这个条目的帮助下,将compareToIgnoreCase
作为Comparator
的desugaring可以分解为以下步骤:
-
List
Collections#sort
需要Comparator
-
Comparator
是一个带有int sort(String, String)
方法的函数接口,它相当于BiFunction
- 因此,比较器实例可以由
BiFunction
兼容的lambda提供:((String a, String b) -> a.compareToIgnoreCase(b)
-
String::compareToIgnoreCase
是指一个带String
参数的实例方法,因此它与上面的lambda兼容:String a
成为接收者,String b
成为方法参数
编辑:从OP输入后我添加了一个解释desugaring的低级示例
- Jersey 2.26:在ResourceConfig中注册@Inject bindFactory无法将Factory转换为Supplier
- Java中JSON未知的字符串
- com.google.gson.internal.LinkedHashTreeMap无法强制转换为我的对象
- 如何从Postgres获取查询计划信息到JDBC
- 使用Jackson序列化一个Double到2的小数位
- 获得价值无法理解
- AWS S3 Java:doesObjectExist导致403:FORBIDDEN
- 如何将一组对象发送到需要所有对象的“id”的NamedQuery。
- Swing中requestFocusInWindow()和grabFocus()之间的区别