Java8中不同参数方法的引用 [英] Reference to methods with different parameters in Java8

查看:50
本文介绍了Java8中不同参数方法的引用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想知道所有这些带有方法引用和功能接口的东西是如何在较低级别上工作的.最简单的例子是我们有一些 List

I'm wondering how does all this stuff with method references and functional interfaces works on lower level. The easiest example is where we have some List

List<String> list = new ArrayList<>();
list.add("b");
list.add("a");
list.add("c"):

现在我们想用 Collections 类对它进行排序,所以我们可以调用:

Now we want to sort it with Collections class, so we can call:

Collections.sort(list, String::compareToIgnoreCase);

但是如果我们定义自定义比较器,它可能是这样的:

But if we define custom comparator it could be something like:

Comparator<String> customComp = new MyCustomOrderComparator<>();
Collections.sort(list, customComp::compare);

问题在于Collections.sort 需要两个参数:List 和Comparator.由于 Comparator 是函数式接口,因此它可以替换为具有相同签名(参数和返回类型)的 lambda 表达式或方法引用.那么,当方法引用 String::compareToIgnoreCase 引用一个实例方法时,当这些方法的签名不匹配时,该方法只接受一个参数时,它是如何工作的呢?Java8中的方法引用是如何翻译的?

The problem is that Collections.sort takes two parameters: List and Comparator. Since Comparator is functional interface it can be replaced with lambda expression or method reference with the same signature (parameters and return type). So how does passing the method reference String::compareToIgnoreCase work when it refers to an instance method that takes only one parameter when the signatures of these methods don't match? How are the method references translated in Java8?

推荐答案

来自 Oracle 方法参考教程:

引用特定类型的任意对象的实例方法

以下是对特定类型的任意对象的实例方法的引用的示例:

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",帕特里夏"、罗伯特"、迈克尔"、琳达"};
Arrays.sort(stringArray, String::compareToIgnoreCase);

方法引用 String::compareToIgnoreCase 的等效 lambda 表达式将具有形式参数列表 (String a, String b),其中 a 和 b 是任意名称,用于更好地描述这个例子.方法引用将调用方法 a.compareToIgnoreCase(b).

String[] stringArray = { "Barbara", "James", "Mary", "John", "Patricia", "Robert", "Michael", "Linda" };
Arrays.sort(stringArray, String::compareToIgnoreCase);

The equivalent lambda expression for the method reference String::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 method a.compareToIgnoreCase(b).

但是,:: 运算符的真正含义是什么?好吧,:: 运算符可以这样描述(来自 这个问题):

But, what does the :: operator really mean? Well, the :: operator can be described like this (from this SO question):

Method Reference 可以在不同的样式中获取,但它们的意思都是一样的:

Method Reference can be obtained in different styles, but they all mean the same:

  1. 一个静态方法(ClassName::methodName)
  2. 特定对象的实例方法 (instanceRef::methodName)
  3. 特定对象的超级方法 (super::methodName)
  4. 特定类型的任意对象的实例方法(ClassName::methodName)
  5. 一个类构造函数引用(ClassName::new)
  6. 一个数组构造函数引用(TypeName[]::new)
  1. A static method (ClassName::methodName)
  2. An instance method of a particular object (instanceRef::methodName)
  3. A super method of a particular object (super::methodName)
  4. An instance method of an arbitrary object of a particular type (ClassName::methodName)
  5. A class constructor reference (ClassName::new)
  6. An array constructor reference (TypeName[]::new)

所以,这意味着方法引用 String::compareToIgnoreCase 属于第二类(instanceRef::methodName),这意味着它可以转换为 (a, b) ->a.compareToIgnoreCase(b).

So, that means that the method reference String::compareToIgnoreCase falls under the second category (instanceRef::methodName) which means that it can be translated to (a, b) -> a.compareToIgnoreCase(b).

我相信以下示例可以进一步说明这一点.Comparator 包含一个方法,该方法对两个 String 操作数进行操作并返回一个 int.这可以伪描述为 (a, b) ==>;返回 int(其中操作数是 ab).如果您这样看,以下所有内容都属于该类别:

I believe that the following examples illustrates this further. A Comparator<String> contains one method that operates on two String operands and returns an int. That can be pseudo-described as (a, b) ==> return int (where the operands are a and b). If you view it that way all of the following falls under that category:

// 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 翻译.

This great SO-answer to explains how lambda functions are compiled. In that answer Jarandinor refers to the following passage from Brian Goetz excellent document that describes more about lambda translations.

我们不是生成字节码来创建实现 lambda 表达式的对象(例如调用内部类的构造函数),而是描述构造 lambda 的方法,并将实际构造委托给语言运行时.该配方编码在调用动态指令的静态和动态参数列表中.

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.

基本上这意味着本机运行时决定如何翻译 lambda.

Basically what this means is that the native runtime decides how to translate the lambda.

布莱恩继续:

方法引用的处理方式与 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 被脱糖变成了一个新方法.例如.

So, lambdas are desugared into a new method. E.g.

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 在文档中也解释了这一点:

But, Brian also explains this in the document:

如果脱糖方法是实例方法,则接收者被认为是第一个参数

if the desugared method is an instance method, the receiver is considered to be the first argument

Brian 继续解释 lambda 的剩余参数作为参数传递给引用的方法.

Brian continues to explan that the lambda’s remaining arguments are passed as arguments to the referred method.

因此,借助 Moandji Ezana 的这篇文章,将 compareToIgnoreCase 脱糖为 Comparator 可以分解为以下步骤:

So, with the help of this entry by Moandji Ezana, the desugaring of compareToIgnoreCase as a Comparator<String> can be broken down to the following steps:

  • Collections#sort 对于 List 需要 Comparator
  • Comparator是一个函数接口,带有int sort(String, String)方法,相当于BiFunction;
  • 因此比较器实例可以由 BiFunction 兼容的 lambda 提供:(String a, String b) ->a.compareToIgnoreCase(b)
  • String::compareToIgnoreCase 指的是一个接受 String 参数的实例方法,因此它与上面的 lambda 兼容:String a成为接收者,String b 成为方法参数
  • Collections#sort for a List<String> expects a Comparator<String>
  • Comparator<String> is a functional interface with the method int sort(String, String), which is equivalent to BiFunction<String, String, Integer>
  • The comparator instance could therefore be supplied by a BiFunction-compatible lambda: (String a, String b) -> a.compareToIgnoreCase(b)
  • String::compareToIgnoreCase refers to an instance method that takes a String argument, so it is compatible with the above lambda: String a becomes the receiver and String b becomes the method argument

从 OP 输入后,我添加了一个 低级 示例来解释 脱糖

After input from the OP I have added a low level example that explains the desugaring

这篇关于Java8中不同参数方法的引用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆