在Java8中引用具有不同参数的方法

时间:2015-01-03 11:30:29

标签: java java-8 method-reference

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

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是功能接口,因此可以使用具有相同签名(参数和返回类型)的lambda表达式或方法引用替换它。那么它是如何工作的,我们也可以传递参考compareTo,它只需要一个参数,这些方法的签名不匹配? 如何在Java8中翻译方法引用?

1 个答案:

答案 0 :(得分:18)

来自Oracle method references tutorial

  

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

     

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

     

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

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

但是,::运算符到底意味着什么?好吧,::运算符可以这样描述(来自this SO question):

  

方法参考可以以不同的样式获得,但它们都意味着相同:

     
      
  1. 静态方法(ClassName::methodName
  2.   
  3. 特定对象(instanceRef::methodName
  4. 的实例方法   
  5. 特定对象的超级方法(super::methodName
  6.   
  7. 特定类型(ClassName::methodName
  8. 的任意对象的实例方法   
  9. 类构造函数引用(ClassName::new
  10.   
  11. 数组构造函数引用(TypeName[]::new
  12.   

因此,这意味着方法引用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

  

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

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

布莱恩继续说道:

  

方法引用的处理方式与lambda表达式相同,不同之处在于大多数方法引用不需要被置于新方法中;我们可以简单地为引用的方法加载一个常量方法句柄,并将其传递给metafactory。

因此,lambdas desugared 进入新方法。例如。

class A {
    public void foo() {
        List<String> list = ...
        list.forEach( s -> { System.out.println(s); } );
    }
}

上面的代码 desugared 就像这样:

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

  

如果desugared方法是实例方法,则接收器被认为是第一个参数

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

因此,在this entry by Moandji Ezana的帮助下,compareToIgnoreCase作为Comparator<String>的贬低可以分解为以下步骤:

  • Collections#sort List<String>预计Comparator<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输入后,我添加了低级别示例,解释了 desugaring