Java SE 8: Lambda Quick Start

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表達式特性
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容