Java 8 Lambda学习笔记

概要

找了几篇Java Lambda表达式的学习文章来看,虽然其他几篇也不错,但参考3(参考4是视频版)是最好的。这里结合自己的理解,重新梳理一下Java Lambda的学习流程。

关于Lambda的概念,Lambda表达式就是匿名方法。以上就是Java 8 Lambda的一句话概括。以下我们结合实例来学习。

基本学习

功能性接口

为了引入Lambda表达式,这里先介绍功能性接口(Functional Interface)。在Java 8中,有且仅有一个方法的接口被称为功能性接口。示例如下。

1
2
3
interface DemoI {
public void demo();
}

最基本的Lambda表达式

概要中说到,Lambda表达式就是匿名方法,实际上,在Java 8以前的版本中是通过匿名类的方式实现匿名方法的,示例如下。

1
2
3
4
5
6
7
8
9
10
public class DemoLambda {
public static void main(String... args) {
DemoI demoI = new DemoI() {
@Override
public void demo() {
System.out.println("Hello Java Lambda.");
}
};
}
}

上面的代码中,匿名类实际有用的代码是demo()方法,而这样一个方法最关键的是它的参数声明,以及方法体中的处理逻辑,至于这个方法实际实现的接口名(DemoI),重写的方法名(demo()),实际上程序员在实现这个匿名类的时候是不关心的。

接下来我们来看Lambda的写法。

1
2
3
4
5
public class DemoLambda {
public static void main(String... args) {
DemoI demoI = () -> System.out.println("Hello Java Lambda.");
}
}

上面的代码中,我们看到

-> System.out.println(“Hello Java Lambda.”)```返回了一个DemoI的匿名类,这句代码和上面的一段代码是等价的。()是方法参数的声明,处理逻辑是```System.out.println(“Hello Java Lambda.”)```。这样,我们的Lambda表达式就完成了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

实际上,Java 8的编译器会帮我们翻译成实现匿名类等价的代码,引入Lambda表达式使我们的代码更简洁易读。

接下来我们来看一个稍微复杂一点的Lambda表达式,我们来完成一个简单的四则运算功能。

```java
interface Operation {
public int operate(int a, int b);
}
public class DemoLambda {
public static void main(String... args) {
// 加法
Operation add = (a, b) -> a + b;
System.out.println(add.operate(4, 2)); // 6
// 减法
Operation minus = (a, b) -> a - b;
System.out.println(minus.operate(4, 2)); // 2
// 乘法
Operation multiple = (a, b) -> a * b;
System.out.println(multiple.operate(4, 2)); // 8
// 除法
Operation devide = (a, b) -> a / b;
System.out.println(devide.operate(4, 2)); // 2
}
}

表达式Lambda和声明式Lambda

在上面的Labmda表达式中,我们的逻辑都只有一句话,或者输出,或者计算加减乘除的值,这种Lambda称为表达式Lambda(Statement Lambda),处理逻辑更复杂的情况我们需要用到声明式Lambda(Expression Lambda),声明式Lambda需要用大括号将多个语句包含起来。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class DemoLambda {
public static void main(String... args) {
// 稍微复杂一点的运算
Operation o = (a, b) -> {
// do something.
int result = 0;
result = a + b;
result /= 2;
return result;
};
System.out.println(o.operate(4, 2)); // 3
}
}

以上就是Java 8中Lambda表达式的基本使用。

进阶学习

用户管理程序V1

先来看以下代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Gender {
public final static int MALE = 1;
public final static int FEMALE = 2;
}
class Person {
private String name;
private Integer age;
private Integer gender;
public Person() {
}
public Person(String name, Integer age, Integer gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
// setters and getters
@Override
public String toString() {
return "Person[name:" + this.name + ",age:" + this.age + ",gender" + this.gender + "]";
}
}

定义了一个Person类(以上Setter和Getter都省略),分别包括姓名,年龄和性别,还定义了一个用于设定性别的Gender类。现在我们还需要一个Admin,他需要对Person进行管理,他还需要找出成年人管理的Person列表中的成年人(age >= 18)和所有女性(gender == Gender.FEMALE),并输出成年人的信息。代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Admin {
private List<Person> personList = new ArrayList<>();
public void addPerson(Person person) {
if (Objects.nonNull(person)) {
this.personList.add(person);
}
}
public void filterAdults() {
for (Person person : this.personList) {
if (person.getAge() >= 18) {
System.out.println(person.toString());
}
}
}
public void filterFemales() {
for (Person person : this.personList) {
if (person.getGender() == Gender.FEMALE) {
System.out.println(person.toString());
}
}
}
// getter
}

Getter省略,Admin中保存了一个Person的List,还有一个filterAdults()方法筛选成年人和filterFemale()方法筛选女性的信息并输出。以下是程序入口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class DemoLambda {
private static Admin admin = new Admin();
static {
admin.addPerson(new Person("Ouyang", 25, Gender.MALE));
admin.addPerson(new Person("Zhao", 27, Gender.MALE));
admin.addPerson(new Person("Qian", 30, Gender.MALE));
admin.addPerson(new Person("Sun", 15, Gender.FEMALE));
admin.addPerson(new Person("Li", 21, Gender.FEMALE));
admin.addPerson(new Person("Enami", 20, Gender.FEMALE));
}
public static void main(String... args) {
admin.filterAdults();
Sysmtem.out.println();
admin.filterFemales();
}
}

输出结果如下。

1
2
3
4
5
6
7
8
Person[name:Ouyang,age:25,gender1]
Person[name:Zhao,age:27,gender1]
Person[name:Qian,age:30,gender1]
Person[name:Li,age:21,gender2]
Person[name:Enami,age:20,gender2]
Person[name:Sun,age:15,gender2]
Person[name:Li,age:21,gender2]
Person[name:Enami,age:20,gender2]

上面的代码完成了我们要的效果,但仍有一些问题。

用户管理程序V2

Lambda表达式的意义在于更支持DRY(Don’t Repeat Yourself)规则以及使代码更简单更易读。上面的代码的问题在于:

  • 为每一个条件单独定义了一个方法,可重用性相对不高,每个筛选标准都需要实现一个方法。
  • 代码弹性低,如果筛选标准改变,需要改变筛选方法中的代码。

为了解决上述问题,我们需要筛选逻辑提取出来,注意是仅提取筛选逻辑部分。修改后的Admin如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Admin {
private List<Person> personList = new ArrayList<>();
public void addPerson(Person person) {
if (Objects.nonNull(person)) {
this.personList.add(person);
}
}
public void filterAdults() {
for (Person person : this.personList) {
if (this.isAdult(person)) {
System.out.println(person.toString());
}
}
}
public void filterFemales() {
for (Person person : this.personList) {
if (this.isFemale(person)) {
System.out.println(person.toString());
}
}
}
public boolean isAdult(Person person) {
return person.getAge() >= 18;
}
public boolean isFemale() {
return person.getGender == Gender.FEMALE;
}
// getter
}

运行结果仍然是正常的,这样,我们单独提取出来了判断是否是成年人的判断逻辑和是否是女性的判断逻辑,但我们还可以改进。

用户管理程序V3

我们可以通过一个泛型的功能性接口来进一步提取判断逻辑,我们可以自定义这个接口,其中需要一个判断方法。但Java 8中已经为我们提供了这样一个接口。在Java 8的java.util.function中,提供了一系列的标准功能性接口。这里我们需要用到的是Predicate接口。

1
2
3
public interface Predicate<T> {
public boolean test(T t);
}

修改后的Admin如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Admin {
private List<Person> personList = new ArrayList<>();
public List<Person> getPersonList() {
return personList;
}
public void addPerson(Person person) {
if (Objects.nonNull(person)) {
this.personList.add(person);
}
}
public void filterPerson(Predicate<Person> personPredicate) {
this.personList.forEach(person -> {
if (personPredicate.test(person)) {
System.out.println(person.toString());
}
});
}
// getter
}

我们的判断逻辑不见了,这是因为我们通过Lambda把我们的代码数据化了,判断逻辑将通过filterPerson()方法的参数传递进来。我们需要进一步修改main()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class DemoLambda {
// 省略初始化
public static void main(String... args) {
// 筛选成人信息
admin.filterPerson(new Predicate<Person>() {
@Override
public boolean test(Person person) {
return person.getAge() >= 18;
}
});
// 筛选女性信息
admin.filterPerson(new Predicate<Person>() {
@Override
public boolean test(Person person) {
return person.getGender() == Gender.FEMALE;
}
});
}
}

上面两个匿名类就很熟悉了,根据我们本文在第二小节中所述,我们可以直接在这里改成Lambda表达式。

1
2
3
4
5
6
7
8
9
10
public class DemoLambda {
// 省略初始化
public static void main(String... args) {
// 筛选成人信息
admin.filterPerson(p -> p.getAge() >= 18);
System.out.println();
// 筛选女性信息
admin.filterPerson(p -> p.getGender() == Gender.FEMALE);
}
}

这样,整个代码比一开始的V1更简洁更易读,这就是Lambda的好处。

输出结果如下。

1
2
3
4
5
6
7
8
Person[name:Ouyang,age:25,gender1]
Person[name:Zhao,age:27,gender1]
Person[name:Qian,age:30,gender1]
Person[name:Li,age:21,gender2]
Person[name:Enami,age:20,gender2]
Person[name:Sun,age:15,gender2]
Person[name:Li,age:21,gender2]
Person[name:Enami,age:20,gender2]

总结

以上就是我们Java 8中Labmda的一般使用方法了,使用Lambda表达式使我们的Java开发更简单,使冗长的代码更精简。

进一步学习的话可以学习如何使用上面提到的java.util.function中各种标准功能性接口。另外Java 8中的一些Collection实现也提供了stream()方法和parallel()方法,分流是为了使用Lambda表达式实现的流式方法和并行式(利用多核CPU)方法,本文就不展开介绍了。

参考

  1. The Inspection Connection – Issue #1, Migration Translation
  2. Java Lambda Expressions
  3. Java SE 8: Lambda Quick Start
  4. Jump-Starting Lambda