java - Java8中不同参数方法的引用

标签 java java-8 method-reference

我想知道所有这些带有方法引用和功能接口(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 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).

但是,:: 是什么意思?运营商究竟是什么意思?嗯,::运算符可以这样描述(来自 this SO question ):

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

  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) .

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

// 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/

相关文章:

java - 如何从String库名称获取jar库名称和版本?

java - 尝试运行 `ConcurrentModificationException` 方法时 ArrayList 抛出 `.size()`

java - tools.jar 的文件位置?

Java,搜索一个元素,没有找到则添加

java - 两种方法引用实例方法的区别

Java 方法引用(将方法作为参数传递)

android-studio - 如何将 Java 方法引用代码转换为 Kotlin

java - 如何加载一个文件以供多种方法访问?

java - JAVA中使用RSA公钥和私钥进行加密和解密

java - 从接口(interface)中的默认方法中访问 super 方法