Java SE 8: Lambda Quick Start
Java SE 8: Lambda Quick Start
施工中
介绍
Lambda表达式是Java SE8的重要新特性,提供了一个实现函数接口的简单方法。Lambda表达式改进了Collection库,使得遍历、查询和提取数据更简单。同时,新的并发机制提高了它们多核环形下的表现。
匿名内部类
匿名内部类提供了声明代码中只出现一次的类的方法。例如,在表中Swing或JavaFX引用中,需要为键盘或鼠标事件声明很多事件处理类。利用匿名内部类,可以这样写:
16 JButton testButton = new JButton("Test Button");
17 testButton.addActionListener(new ActionListener(){
18 @Override public void actionPerformed(ActionEvent ae){
19 System.out.println("Click Detected by Anon Class");
20 }
21 });
否则,每个事件都要单独声明一个类实现ActionListener接口。通过在需要的地方声明内部类,代码更易读一点。但代码仍然不够优雅,因为声明一个内部类仍需要太多代码。
函数接口
ActionListener接口代码如下:
1 package java.awt.event;
2 import java.util.EventListener;
3
4 public interface ActionListener extends EventListener {
5
6 public void actionPerformed(ActionEvent e);
7
8 }
ActionListener是一个只有一个方法的接口,在Java SE8中,这种只有一个方法的接口成为函数接口(之前,这类接口被称为Single Abstract Method type SAM)。
使用内部类实现函数接口在java中普遍适用。Runnable和Comparator也是相同的用法。通过使用Lambda表达式可以改进函数接口的实现。
Lambda表达式的语法
Lambda表达式定位于匿名内部类臃肿的代码实现,将原本5行代码压缩为一个表达式。通过水平途径解决垂直问题。
Lambda表达式由三部分组成
参数Argument List | 箭头 Arrow Token | 主体 Body |
---|---|---|
(int x, int y) | -> | x + y |
主体部分可以是一个表达式或一个代码块。
表达式直接执行并返回。
代码块,代码被当做方法执行,return语句将结果返回给匿名方法的调用者。在代码块中,break 和 continue关键字非法,但在循环体中仍可以使用。
Lambda表达式示例
(int x, int y) -> x + y
() -> 42
(String s) -> { System.out.println(s); }
第一个表达式输入两个int类型参数x、y,使用表达式方式直接返回x+y。
第二个表达式无输入,使用表达式方式返回42.
第三个表达式输入字符串,使用代码块打印字符串,没有返回值。
Runnable Lambda
6 public class RunnableTest {
7 public static void main(String[] args) {
8
9 System.out.println("=== RunnableTest ===");
10
11 // Anonymous Runnable
12 Runnable r1 = new Runnable(){
13
14 @Override
15 public void run(){
16 System.out.println("Hello world one!");
17 }
18 };
19
20 // Lambda Runnable
21 Runnable r2 = () -> System.out.println("Hello world two!");
22
23 // Run em!
24 r1.run();
25 r2.run();
26
27 }
28 }
Comparator Lambda
在Java中,Comparator类用来为集合排序。在下面的例子中,Person实例的队列按照surName属性排序。Person类如下
9 public class Person {
10 private String givenName;
11 private String surName;
12 private int age;
13 private Gender gender;
14 private String eMail;
15 private String phone;
16 private String address;
17 }
使用匿名内部类和Lambda表达式的例子如下:
10 public class ComparatorTest {
11
12 public static void main(String[] args) {
13
14 List<Person> personList = Person.createShortList();
15
16 // Sort with Inner Class
17 Collections.sort(personList, new Comparator<Person>(){
18 public int compare(Person p1, Person p2){
19 return p1.getSurName().compareTo(p2.getSurName());
20 }
21 });
22
23 System.out.println("=== Sorted Asc SurName ===");
24 for(Person p:personList){
25 p.printName();
26 }
27
28 // Use Lambda instead
29
30 // Print Asc
31 System.out.println("=== Sorted Asc SurName ===");
32 Collections.sort(personList, (Person p1, Person p2) -> p1.getSurName().compareTo(p2.getSurName()));
33
34 for(Person p:personList){
35 p.printName();
36 }
37
38 // Print Desc
39 System.out.println("=== Sorted Desc SurName ===");
40 Collections.sort(personList, (p1, p2) -> p2.getSurName().compareTo(p1.getSurName()));
41
42 for(Person p:personList){
43 p.printName();
44 }
45
46 }
47 }
注意到第一个Lambda表达式声明了参数类型,第二个没有声明。Lambda表达式支持 target typing(泛型目标类型推断),通过上下文推断对象的类型。
Listener Lambda
13 public class ListenerTest {
14 public static void main(String[] args) {
15
16 JButton testButton = new JButton("Test Button");
17 testButton.addActionListener(new ActionListener(){
18 @Override public void actionPerformed(ActionEvent ae){
19 System.out.println("Click Detected by Anon Class");
20 }
21 });
22
23 testButton.addActionListener(e -> System.out.println("Click Detected by Lambda Listner"));
24
25 // Swing stuff
26 JFrame frame = new JFrame("Listener Test");
27 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
28 frame.add(testButton, BorderLayout.CENTER);
29 frame.pack();
30 frame.setVisible(true);
31
32 }
33 }
利用Lambda表达式改进代码
Lambda表达式支持了 Don`t repeat yourselt DRY原则,使代码更简洁,更易读。
普通的查询场景
代码中常见的场景是遍历数据集合查找符合条件的数据。给定一群人,不同的查询条件,查询出符合条件的人。
本例中,我们需要找出三类人群:
- 司机:年龄大于16岁
- 适龄兵役者:年龄18到25岁
- 飞行员:年龄23到65岁
查询结果直接打印在控制台,信息包括姓名、年龄和某个特定信息(电邮地址、电话号码)。
Person类
10 public class Person {
11 private String givenName;
12 private String surName;
13 private int age;
14 private Gender gender;
15 private String eMail;
16 private String phone;
17 private String address;
18 }
第一轮
RoboContactsMethods.java
1 package com.example.lambda;
2
3 import java.util.List;
4
5 /**
6 *
7 *
@author MikeW
8 */
9 public class RoboContactMethods {
10
11 public void callDrivers(List<Person> pl){
12 for(Person p:pl){
13 if (p.getAge() >= 16){
14 roboCall(p);
15 }
16 }
17 }
18
19 public void emailDraftees(List<Person> pl){
20 for(Person p:pl){
21 if (p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE){
22 roboEmail(p);
23 }
24 }
25 }
26
27 public void mailPilots(List<Person> pl){
28 for(Person p:pl){
29 if (p.getAge() >= 23 && p.getAge() <= 65){
30 roboMail(p);
31 }
32 }
33 }
34
35
36 public void roboCall(Person p){
37 System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone());
38 }
39
40 public void roboEmail(Person p){
41 System.out.println("EMailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getEmail());
42 }
43
44 public void roboMail(Person p){
45 System.out.println("Mailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getAddress());
46 }
47
48 }
这一实现的缺点:
- 没有遵守DRY原则
- 重复使用循环机制
- 每个查询条件对应一个方法
- 代码无法扩展,如果查询条件发生变化,需要修改代码。
重构查询方法
通过匿名内部类实现。声明MyTest接口,只有一个条件验证函数,返回boolean值。查询条件在方法调用时传递。接口定义如下:
6 public interface MyTest<T> {
7 public boolean test(T t);
8 }
更新后的实现如下:
RoboContactsAnon.java
9 public class RoboContactAnon {
10
11 public void phoneContacts(List<Person> pl, MyTest<Person> aTest){
12 for(Person p:pl){
13 if (aTest.test(p)){
14 roboCall(p);
15 }
16 }
17 }
18
19 public void emailContacts(List<Person> pl, MyTest<Person> aTest){
20 for(Person p:pl){
21 if (aTest.test(p)){
22 roboEmail(p);
23 }
24 }
25 }
26
27 public void mailContacts(List<Person> pl, MyTest<Person> aTest){
28 for(Person p:pl){
29 if (aTest.test(p)){
30 roboMail(p);
31 }
32 }
33 }
34
35 public void roboCall(Person p){
36 System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone());
37 }
38
39 public void roboEmail(Person p){
40 System.out.println("EMailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getEmail());
41 }
42
43 public void roboMail(Person p){
44 System.out.println("Mailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getAddress());
45 }
46
47 }
代码仍然臃肿,可读性不高,每个查询条件都需要单独实现。
Lambda表达式
java.util.function
在Java SE8中提供了JUF包有多个标准函数接口,在本例中,Predicate接口满足我们的需要。
3 public interface Predicate<T> {
4 public boolean test(T t);
5 }
本例最终形态:
RoboContactsLambda.java
1 package com.example.lambda;
2
3 import java.util.List;
4 import java.util.function.Predicate;
5
6 /**
7 *
8 * @author MikeW
9 */
10 public class RoboContactLambda {
11 public void phoneContacts(List<Person> pl, Predicate<Person> pred){
12 for(Person p:pl){
13 if (pred.test(p)){
14 roboCall(p);
15 }
16 }
17 }
18
19 public void emailContacts(List<Person> pl, Predicate<Person> pred){
20 for(Person p:pl){
21 if (pred.test(p)){
22 roboEmail(p);
23 }
24 }
25 }
26
27 public void mailContacts(List<Person> pl, Predicate<Person> pred){
28 for(Person p:pl){
29 if (pred.test(p)){
30 roboMail(p);
31 }
32 }
33 }
34
35 public void roboCall(Person p){
36 System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone());
37 }
38
39 public void roboEmail(Person p){
40 System.out.println("EMailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getEmail());
41 }
42
43 public void roboMail(Person p){
44 System.out.println("Mailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getAddress());
45 }
46
47 }
RoboCallTest04.java
1 package com.example.lambda;
2
3 import java.util.List;
4 import java.util.function.Predicate;
5
6 /**
7 *
8 * @author MikeW
9 */
10 public class RoboCallTest04 {
11
12 public static void main(String[] args){
13
14 List<Person> pl = Person.createShortList();
15 RoboContactLambda robo = new RoboContactLambda();
16
17 // Predicates
18 Predicate<Person> allDrivers = p -> p.getAge() >= 16;
19 Predicate<Person> allDraftees = p -> p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE;
20 Predicate<Person> allPilots = p -> p.getAge() >= 23 && p.getAge() <= 65;
21
22 System.out.println("\n==== Test 04 ====");
23 System.out.println("\n=== Calling all Drivers ===");
24 robo.phoneContacts(pl, allDrivers);
25
26 System.out.println("\n=== Emailing all Draftees ===");
27 robo.emailContacts(pl, allDraftees);
28
29 System.out.println("\n=== Mail all Pilots ===");
30 robo.mailContacts(pl, allPilots);
31
32 // Mix and match becomes easy
33 System.out.println("\n=== Mail all Draftees ===");
34 robo.mailContacts(pl, allDraftees);
35
36 System.out.println("\n=== Call all Pilots ===");
37 robo.phoneContacts(pl, allPilots);
38
39 }
40 }
代码紧凑易读,同时没有重复代码问题。
JUF包:
- Predicate: 传入对象,返回boolean值
- Consumer: 传入对象,没有返回值
- Function: 传入类型T对象,返回类型U对象
- Supplier: 无传入值,返回T类型对象
- UnaryOperator: 一元操作,传入T类型,返回T类型
- BinaryOperator: 二元操作,传入T类型,返回T类型
Lambda表达式与Collections
循环
首先是所有collection类支持的forEach方法。下面的例子展示打印Person队列的各种方法。
Test01ForEach.java
11 public class Test01ForEach {
12
13 public static void main(String[] args) {
14
15 List<Person> pl = Person.createShortList();
16
17 System.out.println("\n=== Western Phone List ===");
18 pl.forEach( p -> p.printWesternName() );
19
20 System.out.println("\n=== Eastern Phone List ===");
21 pl.forEach(Person::printEasternName);
22
23 System.out.println("\n=== Custom Phone List ===");
24 pl.forEach(p -> { System.out.println(p.printCustom(r -> "Name: " + r.getGivenName() + " EMail: " + r.getEmail())); });
25
26 }
27
28 }
18行使用Lambda表达式打印名字,21行使用方法引用调用静态方法,24行注意Lambda表达式嵌套式的参数名。
链式过滤
filter方法接收Predicate实例,过滤集合,返回过滤后的结果。
Test02Filter.java
9 public class Test02Filter {
10
11 public static void main(String[] args) {
12
13 List<Person> pl = Person.createShortList();
14
15 SearchCriteria search = SearchCriteria.getInstance();
16
17 System.out.println("\n=== Western Pilot Phone List ===");
18
19 pl.stream().filter(search.getCriteria("allPilots"))
20 .forEach(Person::printWesternName);
21
22
23 System.out.println("\n=== Eastern Draftee Phone List ===");
24
25 pl.stream().filter(search.getCriteria("allDraftees"))
26 .forEach(Person::printEasternName);
27
28 }
29 }
懒加载
这里的lazy和eager没有想到合适的翻译,保留原文
Getting Lazy
通过向Collection包种加入新的枚举方式,java开发人员可以做更多的代码优化。
Laziness:指系统仅在必要时处理必须处理的对象。在上面的例子中,forEach是lazy模式的,因为这次遍历仅仅涉及两个Person对象,后续操作只发生在过滤后的对象上,代码的效率提高了。
Eagerness:代码遍历整个对象队列执行操作。
通过将forEach加入collection包,代码可以在合适的地方进行Lazy优化,在其他需要eager模式(如求和或求平均值)的地方仍使用eager模式。这一方式使得代码更高效,更有弹性。
流方法
stream方法以Collection对象为输入,以StreamInterface对象作为输出。Stream就像Iterator,只能遍历一次,不能修改其中的对象。Stream支持单线程和并行执行。
获取结果集
Stream操作的结果可以通过创建新的collection对象保存。下面的例子展示了如何将集合遍历的结果存入新的集合对象中。
Test03toList.java
10 public class Test03toList {
11
12 public static void main(String[] args) {
13
14 List<Person> pl = Person.createShortList();
15
16 SearchCriteria search = SearchCriteria.getInstance();
17
18 // Make a new list after filtering.
19 List<Person> pilotList = pl
20 .stream()
21 .filter(search.getCriteria("allPilots"))
22 .collect(Collectors.toList());
23
24 System.out.println("\n=== Western Pilot Phone List ===");
25 pilotList.forEach(Person::printWesternName);
26
27 }
28
29 }
集合计算
下面的例子展示了如何利用map方法获取对象的某个值,然后执行计算操作。注意Stream是并行执行的,返回值也略有不同。
Test04Map.java
10 public class Test04Map {
11
12 public static void main(String[] args) {
13 List<Person> pl = Person.createShortList();
14
15 SearchCriteria search = SearchCriteria.getInstance();
16
17 // Calc average age of pilots old style
18 System.out.println("== Calc Old Style ==");
19 int sum = 0;
20 int count = 0;
21
22 for (Person p:pl){
23 if (p.getAge() >= 23 && p.getAge() <= 65 ){
24 sum = sum + p.getAge();
25 count++;
26 }
27 }
28
29 long average = sum / count;
30 System.out.println("Total Ages: " + sum);
31 System.out.println("Average Age: " + average);
32
33
34 // Get sum of ages
35 System.out.println("\n== Calc New Style ==");
36 long totalAge = pl
37 .stream()
38 .filter(search.getCriteria("allPilots"))
39 .mapToInt(p -> p.getAge())
40 .sum();
41
42 // Get average of ages
43 OptionalDouble averageAge = pl
44 .parallelStream()
45 .filter(search.getCriteria("allPilots"))
46 .mapToDouble(p -> p.getAge())
47 .average();
48
49 System.out.println("Total Ages: " + totalAge);
50 System.out.println("Average Age: " + averageAge.getAsDouble());
51
52 }
53
54 }
总结
- 匿名内部类
- Lambda表达式替代匿名内部类
- Lambda表达式的语法
- Function包的Predicate借口实现集合过滤操作
- Collections包中增加的Lambda表达式特性