方法引用


使用Lambda表达式,我们就可以不必编写FunctionalInterface接口的实现类,从而简化代码:

  1. Arrays.sort(array, (s1, s2) -> {
  2. return s1.compareTo(s2);
  3. });

实际上,除了Lambda表达式,我们还可以直接传入方法引用。例如:

方法引用 - 图1

上述代码在Arrays.sort()中直接传入了静态方法cmp的引用,用Main::cmp表示。

因此,所谓方法引用,是指如果某个方法签名和接口恰好一致,就可以直接传入方法引用。

因为Comparator<String>接口定义的方法是int compare(String, String),和静态方法int cmp(String, String)相比,除了方法名外,方法参数一致,返回类型相同,因此,我们说两者的方法签名一致,可以直接把方法名作为Lambda表达式传入:

  1. Arrays.sort(array, Main::cmp);

注意:在这里,方法签名只看参数类型和返回类型,不看方法名称,也不看类的继承关系。

我们再看看如何引用实例方法。如果我们把代码改写如下:

方法引用 - 图2

不但可以编译通过,而且运行结果也是一样的,这说明String.compareTo()方法也符合Lambda定义。

观察String.compareTo()的方法定义:

  1. public final class String {
  2. public int compareTo(String o) {
  3. ...
  4. }
  5. }

这个方法的签名只有一个参数,为什么和int Comparator<String>.compare(String, String)能匹配呢?

因为实例方法有一个隐含的this参数,String类的compareTo()方法在实际调用的时候,第一个隐含参数总是传入this,相当于静态方法:

  1. public static int compareTo(this, String o);

所以,String.compareTo()方法也可作为方法引用传入。

构造方法引用

除了可以引用静态方法和实例方法,我们还可以引用构造方法。

我们来看一个例子:如果要把一个List<String>转换为List<Person>,应该怎么办?

  1. class Person {
  2. String name;
  3. public Person(String name) {
  4. this.name = name;
  5. }
  6. }
  7. List<String> names = List.of("Bob", "Alice", "Tim");
  8. List<Person> persons = ???

传统的做法是先定义一个ArrayList<Person>,然后用for循环填充这个List

  1. List<String> names = List.of("Bob", "Alice", "Tim");
  2. List<Person> persons = new ArrayList<>();
  3. for (String name : names) {
  4. persons.add(new Person(name));
  5. }

要更简单地实现StringPerson的转换,我们可以引用Person的构造方法:

方法引用 - 图3

后面我们会讲到Streammap()方法。现在我们看到,这里的map()需要传入的FunctionalInterface的定义是:

  1. @FunctionalInterface
  2. public interface Function<T, R> {
  3. R apply(T t);
  4. }

把泛型对应上就是方法签名Person apply(String),即传入参数String,返回类型Person。而Person类的构造方法恰好满足这个条件,因为构造方法的参数是String,而构造方法虽然没有return语句,但它会隐式地返回this实例,类型就是Person,因此,此处可以引用构造方法。构造方法的引用写法是类名::new,因此,此处传入Person::new

练习

方法引用 - 图4下载练习:使用方法引用实现忽略大小写排序 (推荐使用IDE练习插件快速下载)

小结

FunctionalInterface允许传入:

  • 接口的实现类(传统写法,代码较繁琐);
  • Lambda表达式(只需列出参数名,由编译器推断类型);
  • 符合方法签名的静态方法;
  • 符合方法签名的实例方法(实例类型被看做第一个参数类型);
  • 符合方法签名的构造方法(实例类型被看做返回类型)。

FunctionalInterface不强制继承关系,不需要方法名称相同,只要求方法参数(类型和数量)与方法返回类型相同,即认为方法签名相同。

读后有收获可以支付宝请作者喝咖啡:

方法引用 - 图5