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 {
public int compare(Person a, Person b) {
return a.getBirthday().compareTo(b.getBirthday());
}
}
Arrays.sort(rosterAsArray, new PersonAgeComparator());
我们调用到的sort
方法的签名如下:
static 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.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 , DEST extends Collection>
DEST transferElements(
SOURCE sourceCollection,
Supplier collectionFactory) {
DEST result = collectionFactory.get();
for (T t : sourceCollection) {
result.add(t);
}
return result;
}
函数式接口Supplier
只包含一个方法get
, 没有参数,它返回一个对象。相应的,你可以在调用 方法transferElements
的时候,使用Lambda表达式:
Set rosterSetLambda =
transferElements(roster, () -> { return new HashSet<>(); });
如果用构造方法引用替代Lambda表达式,就是如下:
Set rosterSet = transferElements(roster, HashSet::new);
Java编译器会推断你想创建一个Haset的集合, 并且用来存储Person元素。当然,你还可以用下面的方式表达:
Set rosterSet = transferElements(roster, HashSet::new);