Lambda表达式可以用来创建匿名方法。有时候,一个Lambda表达式只是调用了一下已经存在的方法, 其它的啥也没干。在这种情况下,如果能够直接引用方法的名字会使代码看起来很清爽简洁。方法引用就能满足这一点,它们就是紧凑、易读的用Lambda表达式。
让我们再来看一看之前在Lambda表达式章节里提到的Person类:
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 int compareByAge(Person a, Person b) {
return a.birthday.compareTo(b.birthday);
}}
假定你的社交网站的成员都存储在一个数组里, 你想根据年龄把成员排序。你可以用下面的代码 (如果要查看更多详细,可以参看这里 MethodReferencesTest)
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());
我们调用到的sort
方法的签名如下:
static <T> void sort(T[] a, Comparator<? super T> c)
请注意,接口Comparator
是一个函数式接口。因此你可以直接使用Lambda表达式, 而没有必要创建一个实现接口的类再实例化它。
Arrays.sort(rosterAsArray,
(Person a, Person b) -> {
return a.getBirthday().compareTo(b.getBirthday());
}
);
然而,你仔细观察的话会发现,其实这个比较出生日期的方法已经存在了,就是Person.compareByAge
, 因此你可以直接调用这个方法,而不用在Lambda表达式主体里再实现一遍。
Arrays.sort(rosterAsArray,
(a, b) -> Person.compareByAge(a, b)
);
因为Lambda表达式调用了一个已经存在的方法,你可以用方法引用来代替Lambda表达式。
Arrays.sort(rosterAsArray, Person::compareByAge);
方法引用Person::compareByAge
和 Lambda表达式(a, b) -> Person.compareByAge(a, b)
在语义上完全一样的, 它们都有下列特征:
- 它们的参数都是来自于
Comparator<Person>.compare
,也就是Person, Person)
- 它们的主体都会调用
Person.compareByAge.
方法引用的种类
总共有4种方法引用:
类别 | 示例 |
---|---|
引用静态方法 | ContainingClass::staticMethodName |
引用一个特定对象的实例方法 | containingObject::instanceMethodName |
引用一个特定类型的任意对象的实例方法 | ContainingType::methodName |
引用构造方法 | ClassName::new |
引用静态方法
Person::compareByAge
就是一种静态方法的引用。
引用一个特定对象的实例方法
下面的例子就是引用一个特定对象的实例方法:
class ComparisonProvider {
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, myComparisonProvider::compareByName);
方法引用myComparisonProvider::compareByName
会调用compareByName
, 这是对象myComparisonProvider
的一部分。JRE会推断方法的类型参数,在这种情况下就是(Person, Person)
.
引用一个特定类型的任意对象的实例方法
下面的例子是引用一个特定类型的任意对象的实例方法
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)
引用构造方法
引用构造方法和引用静态方法很相似,只是在这里你用new关键字。下面的方法是把元素从一个集合复制到另一个:
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;
}
函数式接口Supplier
只包含一个方法get
, 没有参数,它返回一个对象。相应的,你可以在调用 方法transferElements
的时候,使用Lambda表达式:
Set<Person> rosterSetLambda =
transferElements(roster, () -> { return new HashSet<>(); });
如果用构造方法引用替代Lambda表达式,就是如下:
Set<Person> rosterSet = transferElements(roster, HashSet::new);
Java编译器会推断你想创建一个Haset的集合, 并且用来存储Person元素。当然,你还可以用下面的方式表达:
Set<Person> rosterSet = transferElements(roster, HashSet<Person>::new);