java8 之如何使用 lambda 表达式

字数1,747 大约花费8分钟

目录

  1. 1. 使用匿名内部类的例子
  2. 2. 小结

上一篇文章 中介绍了 lambda 表达式的语法,引入了 lambda 表达式的使用场景,以及使用 lambda 表达式的好处。我们将在这篇文章中,已实例讲解如何定义和使用 lambda 表达式,以及与其它语言相比,lambda 表达式在 Java 中的特殊规范。

使用匿名内部类的例子

首先明确一点,在 Java8 出现之前,lambda 表达式能够做到的,使用内部类也能做到,lambda 表达式只是简化了编程。
下面的例子是从列表中根据条件挑选出读者。

定义 TantanitReader:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public class TantanitReader {
private int age;
private String loginName;
private String realName;
private String career;

public TantanitReader() {
}

public TantanitReader(int age, String loginName, String realName, String career) {
this.age = age;
this.loginName = loginName;
this.realName = realName;
this.career = career;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public String getLoginName() {
return loginName;
}

public void setLoginName(String loginName) {
this.loginName = loginName;
}

public String getRealName() {
return realName;
}

public void setRealName(String realName) {
this.realName = realName;
}

public String getCareer() {
return career;
}

public void setCareer(String career) {
this.career = career;
}

@Override
public String toString() {
return "age:"+this.getAge()+",loginName:"+this.loginName
+",realName:"+this.getRealName()+",career:"+this.getCareer();
}
}

定义判断的接口:

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

定义选择函数:

1
2
3
4
5
6
7
8
9
10
11
12
public class SelectService<T> {
public List<T> select(Collection<T> source, Predicate<T> predicate){
List result = new LinkedList();
for(T element:source){
if (predicate.test(element)) {
result.add(element);
}
}
return result;
}

}

编写测试用的例子,分别选择成年读者和十多岁(包括 10 岁)的读者:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class TantanitReaderPredicateTest {


public static void main(String[] args) {
SelectService tantanitReaderSelectSerive
=new SelectService<TantanitReader>();
List<TantanitReader> source = new LinkedList<>();
source.add(new TantanitReader(10,"jack","张三","学生"));
source.add(new TantanitReader(18,"rose","李四","学生"));
source.add(new TantanitReader(19,"mike","王五","程序员"));
source.add(new TantanitReader(20,"jack","赵六","作家"));

List<TantanitReader> audultReaders
=tantanitReaderSelectSerive.select(source, new Predicate() {
@Override
public boolean test(Object o) {
TantanitReader tantanitReader=(TantanitReader)o;
return tantanitReader.getAge()>=18;
}
});
System.out.println("tantanit.com 成年读者名单如下:");
printTantanitReaders(audultReaders);

System.out.println("tantanit.com 十多岁(包含 10 岁)成员如下:");
List<TantanitReader> teenReaders
=tantanitReaderSelectSerive.select(source, new Predicate() {
@Override
public boolean test(Object o) {
TantanitReader tantanitReader=(TantanitReader)o;
return tantanitReader.getAge()>=10 && tantanitReader.getAge()<=19;
}
});
printTantanitReaders(teenReaders);
}


public static void printTantanitReaders(List<TantanitReader> tantanitReaders) {
for (TantanitReader tantanitReader : tantanitReaders) {
System.out.println(tantanitReader.toString());
}
}


}

执行后,打印结果如下:

1
2
3
4
5
6
7
8
tantanit.com 成员读者名单如下:
age:18,loginName:rose,realName: 李四,career: 学生
age:19,loginName:mike,realName: 王五,career: 程序员
age:20,loginName:jack,realName: 赵六,career: 作家
tantanit.com 十多岁(包含10 岁)成员如下:
age:10,loginName:jack,realName: 张三,career: 学生
age:18,loginName:rose,realName: 李四,career: 学生
age:19,loginName:mike,realName: 王五,career: 程序员

可以看到,两次选择读者,都需要 new Predicate(),并且重写(Override)test 方法,而真正的差异其实只在于判断语句:

1
tantanitReader.getAge()>=18

1
tantanitReader.getAge()>=10 && tantanitReader.getAge()<=19

但是在 Java8 之前,由于没有 lambda 表达式,只能忍受这种冗余。如何用 lambda 表达式来简化代码呢?

为了照顾 Java 开发人员既有的编程习惯,与其它语言不同,Java8 在设计 lambda 表达式的使用机制时,规定仍然需要使用接口,并且要求所使用的接口必须是函数式接口,在这个例子中,我们仍然可以使用:

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

因为这个接口只有一个抽象方法(java8 引入了 default 方法,default 方法有具体实现,不算抽象方法),所以它是函数式接口(functional interface)。函数式接口可以加上 @FunctionalInterface 声明,也可以不加。但是加上之后,编译器在编译阶段就会检查这个接口是否符合函数式接口的定义,所以这里我们定义一个新的接口,并且加上 @FunctionalInterface 声明:

1
2
3
4
@FunctionalInterface
public interface PredicateFunction<T> {
boolean test(T t);
}

并且给 SelectService 添加一个以 PredicateFunction 为参数的方法:

1
2
3
4
5
6
7
8
9
public List<T> select(Collection<T> source, PredicateFunction<T> predicate){
List result = new LinkedList();
for(T element:source){
if (predicate.test(element)) {
result.add(element);
}
}
return result;
}

再修改测试的例子:

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
30
31
32
33
34
35
36
public class TantanitReaderPredicateFunctionTest {

public static void main(String[] args) {
SelectService tantanitReaderSelectSerive
=new SelectService<TantanitReader>();
List<TantanitReader> source = new LinkedList<>();
source.add(new TantanitReader(10,"jack","张三","学生"));
source.add(new TantanitReader(18,"rose","李四","学生"));
source.add(new TantanitReader(19,"mike","王五","程序员"));
source.add(new TantanitReader(20,"jack","赵六","作家"));

PredicateFunction<TantanitReader> predicateFunction
= (TantanitReader tantanitReader) -> tantanitReader.getAge() >= 18;
List<TantanitReader> audultReaders
=tantanitReaderSelectSerive.select(source,predicateFunction);

System.out.println("tantanit.com 成员读者名单如下:");
printTantanitReaders(audultReaders);

System.out.println("tantanit.com 十多岁(包含 10 岁)成员如下:");
PredicateFunction<TantanitReader> predicateFunction2
= (TantanitReader tantanitReader)
-> tantanitReader.getAge()>=10 && tantanitReader.getAge()<=19;
List<TantanitReader> teenReaders
=tantanitReaderSelectSerive.select(source,predicateFunction2);
printTantanitReaders(teenReaders);
}


public static void printTantanitReaders(List<TantanitReader> tantanitReaders) {
for (TantanitReader tantanitReader : tantanitReaders) {
System.out.println(tantanitReader.toString());
}
}

}

下面我们分析一下这段代码是如何生效的:

1
2
3
4
PredicateFunction<TantanitReader> predicateFunction
= (TantanitReader tantanitReader) -> tantanitReader.getAge() >= 18;
List<TantanitReader> audultReaders
=tantanitReaderSelectSerive.select(source,predicateFunction);

这段代码,生成了一个 PredicateFunction 类型的实例,并且将该实例的引用作为参数传给 tantanitReaderSelectSerive 的 select 方法,并且执行 select 方法。select 在执行过程中,调用 predicateFunction 的 test 方法,而 test 方法的内容就是我们传入的 lambda 表达式,最终按照 lambda 表达式,选择出读者。

再进一步,一般可以不定义 predicateFunction 这个变量,而直接将 lambda 表达式作为参数传给 tantanitReaderSelectSerive 的 select 方法,像这样:

1
2
3
4
List<TantanitReader> audultReaders
=tantanitReaderSelectSerive.select(
source,(TantanitReader tantanitReader) -> tantanitReader.getAge() >= 18
);

但是这个例子,实际上会报编译错误,说 TantanitReader 和 tantanitReaderSelectSerive 的 select 方法的定义不匹配,因为 select 方法使用的是泛型。java8 的文档确实是规定了在使用泛型的情况下,不能直接将 lambda 表达式作为参数,这个挺无语的。如果不使用泛型的,没有这个问题。

小结

下面总结一下如何使用 lambda 表达式

  1. 首先,定义一个函数式接口(functional interface),并且在接口中定义需要使用的抽象方法。
  2. 编写业务方法,并且以该函数式接口作为参数,并且调用该接口定义的方法,完成业务逻辑。
  3. 调用业务方法,并且将 lambda 表达式作为参数传入。

如果使用了泛型,最后一步改为先定义一个函数式接口的实例的引用,再作为参数传给业务方法。

此外,lambda 表达式还可以继续简化为函数引用,将在后面的文章中讲解。

谈谈 IT的文章均为原创或翻译(翻译会注明外文来源),转载请以链接形式标明本文地址: http://tantanit.com/java-how-to-use-lambda/

谈谈IT

欢迎关注官方微信公众号获取最新原创文章