Lambda表达式
前言:本文是基于ORACLE官网的JavaTM Tutorials的Lambda Expressions章节整理而成。
使用匿名类可能导致程序不够简洁,那么JDK8开始,出现了Lambda表达式可以很好地解决这个问题。
接下来我们通过一个案例,一步一步地理解Lambda表达式:
需求:你要创建一个社交网站的应用,该应用有这个功能:管理员能够进行各种类型的操作,比如给满足特定条件的用户发送email消息。
-
该应用的用户用Person类表示,并且所有成员存储在
List<Person>
实例中。public class Person { public enum Sex { MALE, FEMALE } String name; LocalDate birthday; Sex gender; String emailAddress; public int getAge() { // ... } public void printPerson() { // ... } }
Approach 1: Create Methods That Search for Members That Match One Characteristic
最简单的方法是创建几个方法,每个方法返回复合条件的用户。比如下面的printPersonsOlderThan()
会打印出大于age的用户。
public static void printPersonsOlderThan(List<Person> roster, int age) {
for (Person p : roster) {
if (p.getAge() >= age) {
p.printPerson();
}
}
}
这么做显然太low了。
Approach 2: Create More Generalized Search Methods
第二种方法返回年龄在low和high之间的用户,比第一种好一点,但也很low。
public static void printPersonsWithinAgeRange(
List<Person> roster, int low, int high) {
for (Person p : roster) {
if (low <= p.getAge() && p.getAge() < high) {
p.printPerson();
}
}
}
Approach 3: Specify Search Criteria Code in a Local Class
第三种方法比前两种方法逼格高一点,可以定制CheckPerson
的条件,当需求改变时,不用修改printPersons()
的结构。但是却引入了其他的代码(接口,自定义实现类)。
//过滤条件的接口
interface CheckPerson {
boolean test(Person p);
}
//实现过滤条件接口的类,在test()写条件。
class CheckPersonEligibleForSelectiveService implements CheckPerson {
public boolean test(Person p) {
return p.gender == Person.Sex.MALE &&
p.getAge() >= 18 &&
p.getAge() <= 25;
}
}
public static void printPersons(
List<Person> roster, CheckPerson tester) {
for (Person p : roster) {
if (tester.test(p)) {
p.printPerson();
}
}
}
Approach 4: Specify Search Criteria Code in an Anonymous Class
继续改进,在匿名类中确定查询条件。这一步理解起来应该没什么问题。
printPersons(
roster,
new CheckPerson() {
public boolean test(Person p) {
return p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25;
}
}
);
Approach 5: Specify Search Criteria Code with a Lambda Expression
我们的主角Lambda表达式姗姗来迟。这里介绍一个概念:函数式接口。所谓函数式接口就是只含有一个抽象方法的接口。(一个函数式接口可能会含有一个或多个default methods
或 static methods
)。你可以省略方法名字当你实现它时。
printPersons(
roster,
(Person p) -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25
);
Approach 6: Use Standard Functional Interfaces with Lambda Expressions
分析到这里,我们还是自己写了个接口。其实Java8替我们考虑了这些,在java.util.function
包下为我们提供了一系列Standard Functional Interfaces(标准函数式接口)。比如在本例中,可以用Predicate<T>
代替CheckPerson接口。
//可以看到Predicate<T>接口也有返回值为boolean的test方法
interface Predicate<T> {
boolean test(T t);
}
//新的方法定义
public static void printPersonsWithPredicate(
List<Person> roster, Predicate<Person> tester) {
for (Person p : roster) {
if (tester.test(p)) {
p.printPerson();
}
}
}
//新的执行方式
printPersonsWithPredicate(
roster,
p -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25
);
Approach 7: Use Lambda Expressions Throughout Your Application
上面的方法中不只有一处可用Lambda表达式替换。重新思考下:
如果你想对满足查询条件的用户做一些其他的功能,而不是printPerson()
时,可以用Lambda表达式实现一个参数为Person
,返回值为void
的函数式接口。Java也帮我们想好了, Consumer<T>
接口包含了 void accept(T t)
方法。
//使用Consumer<T>接口作为方法参数
public static void processPersons(
List<Person> roster,
Predicate<Person> tester,
Consumer<Person> block) {
for (Person p : roster) {
if (tester.test(p)) {
block.accept(p);
}
}
}
//新的实现方式
processPersons(
roster,
p -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25,
p -> p.printPerson()
);
如果你想对用户有更多的操作,比如检索他们的联系方式。那么这时,你需要一个有返回值的接口。Java也为我们提供了,他就是Function<T,R>
接口,它有 R apply(T t)
方法。
//这样我们在block.accept()中处理的对象就是经过tester判别,mapper返回之后的对象。
public static void processPersonsWithFunction(
List<Person> roster,
Predicate<Person> tester,
Function<Person, String> mapper,
Consumer<String> block) {
for (Person p : roster) {
if (tester.test(p)) {
String data = mapper.apply(p);
block.accept(data);
}
}
}
//新的实现
processPersonsWithFunction(
roster,
p -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25,
//将Person映射成String
p -> p.getEmailAddress(),
email -> System.out.println(email)
);
Approach 8: Use Generics More Extensively
使用泛型进一步扩展通用性。
//注意方法声明中的<X,Y>
public static <X, Y> void processElements(
Iterable<X> source,
Predicate<X> tester,
Function <X, Y> mapper,
Consumer<Y> block) {
for (X p : source) {
if (tester.test(p)) {
Y data = mapper.apply(p);
block.accept(data);
}
}
}
//打印符合查询条件的用户的email地址,可以这样调用processElements()
//和Approach7种调用方法一样。
processElements(
roster,
p -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25,
p -> p.getEmailAddress(),
email -> System.out.println(email)
);