Java SE 8: Lambda Quick Start
施工中
介紹
Lambda表達式是Java SE8的重要新特性,提供了一個實現函數接口的簡單方法。Lambda表達式改進了Collection庫,使得遍歷、查詢和提取數據更簡單。同時,新的并發(fā)機制提高了它們多核環(huán)形下的表現。
匿名內部類
匿名內部類提供了聲明代碼中只出現一次的類的方法。例如,在表中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接口。通過在需要的地方聲明內部類,代碼更易讀一點。但代碼仍然不夠優(yōu)雅,因為聲明一個內部類仍需要太多代碼。
函數接口
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 |
主體部分可以是一個表達式或一個代碼塊。
表達式直接執(zhí)行并返回。
代碼塊,代碼被當做方法執(zhí)行,return語句將結果返回給匿名方法的調用者。在代碼塊中,break 和 continue關鍵字非法,但在循環(huán)體中仍可以使用。
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原則
- 重復使用循環(huán)機制
- 每個查詢條件對應一個方法
- 代碼無法擴展,如果查詢條件發(fā)生變化,需要修改代碼。
重構查詢方法
通過匿名內部類實現。聲明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 }
本例最終形態(tài):
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
循環(huán)
首先是所有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行使用方法引用調用靜態(tài)方法,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開發(fā)人員可以做更多的代碼優(yōu)化。
Laziness:指系統(tǒng)僅在必要時處理必須處理的對象。在上面的例子中,forEach是lazy模式的,因為這次遍歷僅僅涉及兩個Person對象,后續(xù)操作只發(fā)生在過濾后的對象上,代碼的效率提高了。
Eagerness:代碼遍歷整個對象隊列執(zhí)行操作。
通過將forEach加入collection包,代碼可以在合適的地方進行Lazy優(yōu)化,在其他需要eager模式(如求和或求平均值)的地方仍使用eager模式。這一方式使得代碼更高效,更有彈性。
流方法
stream方法以Collection對象為輸入,以StreamInterface對象作為輸出。Stream就像Iterator,只能遍歷一次,不能修改其中的對象。Stream支持單線程和并行執(zhí)行。
獲取結果集
Stream操作的結果可以通過創(chuàng)建新的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方法獲取對象的某個值,然后執(zhí)行計算操作。注意Stream是并行執(zhí)行的,返回值也略有不同。
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表達式特性