方法引用

你可以使用 lambda 表达式来创建匿名方法,然而,有时候 lambda 表达式只是用来调用一个已存在的方法。这种情况下,直接调用方法的名字会更简单直接。方法引用就是干这种事的。

简介

用下面的 Person 类来做一个说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Person {
public enum Sex {
MALE, FEMALE;
}

String name;
LocalDate birthday;
Sex gender;
String emailAddress;
public int getAge() {
// ...
}

public Calendar getBirthday() {
return birthday;
}

public static compareByAge(Persion a, Person b) {
return a.birthday.compareTo(b.birthday);
}
}

假设服务器返回给你的是一个数组,然后你希望根据 age 来排序,你就可能会写出如下的代码:

1
2
3
4
5
6
7
8
9
Person[] rosterAsArray = roster.toArray(new Person[roster.size()]);

class PersonAgeComparator implements Comparator<Person> {
public int compare(Person a, Person b) {
return a.getBirthday().compareTo(b.getBirthday());
}
}

Arrays.sort(rosterAsArray, new PersonAgeComparator());

调用排序的方法签名如下:

1
static <T> void sort (T[] a, Comparator<? super T> c)

注意到 Comparator 接口是一个方法接口,因此,你可以使用 lambda 表达式来定义并创建一个实现了 Comparator 接口的对象:

1
2
3
4
Arrays.sort(rosterAsArray, (Person a, Person b) -> {
return a.getBirthday().compareTo(b.getBirthday());
}
);

并且,这个比较生日大小的方法已经存在了,就是 Person.compareByAge。你可以在 lambda 表达式中调用这个方法:

1
Arrays.sort(rosterAsArray, (a, b) -> Person.compareByAge(a, b));

由于 lambda 表达式只是调用了一个已存在的方法,你可以使用方法引用来代替 lambda 表达式:

1
Arrays.sort(rosterAsArray, Person::compareByAge);

其中,Person::compareByAge 在语义上等同于 lambda 表达式 (a, b) -> Person.compareByAge(a, b)。两者都有如下的特征:

  • 参数列表和 Comparator.compare 一样,都是 (Person, Person)。
  • 都是调用同一个方法:Person.compareByAge。

方法引用的类型

方法引用一共有四种类型:

类型 举例
引用静态方法 ContainingClass::staticMethodName
引用某个对象的实例方法 containingObject::instanceMethodName
引用任意内置类的实例方法 ContainingType::methodName
引用构造方法 ClassName::new

下面分别介绍着四种类型:

  • 引用静态方法

前面的 Person::compareByAge 就是一个静态方法引用。

  • 引用某个对象的实例方法

用下面这个例子做说明:

1
2
3
4
5
6
7
8
9
10
11
12
class ComparsionProvider {
public int compareByName(Person a, Person b) {
return a.getName().compareTo(b.getName());
}

public int compareByAge(Person a, Person b) {
return a.getBirthday().compareTo(b.getBirthday());
}
}

ComparisonProvider myComparisonProvider = new ComparisonProvider();
Arrays.sort(rosterAsArray, myComparionProvider::compareByName);

myComparisonProvider::compareByName 这个方法引用调用了 myComparisonProvider 对象的一个 compareByName 方法。JRE 可以推测方法的参数类型,比如在这个例子中就是 (Person, Person)。

  • 引用任意内置类的实例方法

用下面这个例子做说明:

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

和方法引用 String::compareToIgnoreCase 等价的 lambda 表达式会有一个参数列表 (String a, String b),而方法引用会直接调用方法 a.compareToIgnoreCase(b)。

  • 引用构造方法

和引用静态方法类似,你只需要调用 new 这个方法就行了,下面举例说明:

1
2
3
4
5
6
7
public static <T, SOURCE extends Collection<T>, DEST extends Collection<T>> DEST transferElements (SOURCE sourceCollection, Supplier<DEST> collectionFactory) [
DEST result = collectionFactory.get();
for (T t : sourceCollection) {
result.add(t);
}
return result;
]

你可以像下面那样使用 lambda 调用 transferElements 方法:

1
Set<Person> rosterSetLambda = transferElements(roster, () -> {return new HashSet<>();});

也可以使用方法引用:

1
Set<Person> rosterSet = transferElements(roster, HashSet::new);

Java 编译器会推测出你想要创建一个 HashSet 集合来存储 Person 对象,当然,你也可以使用下面的方法来指明对象类型:

1
Set<Person> rosterSet = transferElements(roster, HashSet<Person>::new);
如果觉得文章对你有帮助,请我喝杯可乐吧