掌握 Java 8 Lambda 表達式

tmac6438 8年前發布 | 20K 次閱讀 Java8 Lambda

Lambda 表達式 是 Java8 中最重要的功能之一。使用 Lambda 表達式 可以替代只有一個函數的接口實現,告別匿名內部類,代碼看起來更簡潔易懂。Lambda 表達式 同時還提升了對 集合 框架的迭代、遍歷、過濾數據的操作。

匿名內部類

在 Java 世界中,匿名內部類 可以實現在應用程序中可能只執行一次的操作。例如,在 Android 應用程序中,一個按鈕的點擊事件處理。你不需要為了處理一個點擊事件單獨編寫一個獨立的類,可以用匿名內部類完成該操作:

Button button = (Button) findViewById(R.id.button1);
button.setOnClickListener(new OnClickListener() {

    @Override
    public void onClick(View view) {
        Toast.makeText(MainActivity.this, "Button Clicked", Toast.LENGTH_SHORT).show();
    }

});

通過匿名內部類,雖然代碼看起來不是很優雅,但是代碼看起來比使用單獨的類要好理解,可以直接在代碼調用的地方知道點擊該按鈕會觸發什么操作。

Functional Interfaces(函數型接口)

定義 OnClickListener 接口的代碼如下:

public interface OnClickListener {
        void onClick(View v);
    }

OnClickListener 是一個只有一個函數的接口。在 Java 8 中,這種只有一個函數的接口被稱之為 “Functional Interface”。

在 Java 中 Functional Interface 用匿名內部類實現是一種非常常見的形式。除了 OnClickListener 接口以外,像 Runnable 和 Comparator 等接口也符合這種形式。

Lambda 表達式語法

Lambda 表達式通過把匿名內部類五行代碼簡化為一個語句。這樣使代碼看起來更加簡潔。

一個 Lambda 表達式 由三個組成部分:

參數列表 箭頭符號 函數體(int x, int y) -> x + y

函數體可以是單個表達式,也可以是代碼塊。如果是單個表達式的話,函數體直接求值并返回了。如果是代碼塊的話,就和普通的函數一樣執行,return 語句控制調用者返回。在最外層是不能使用 break 和 continue 關鍵字的,在循環中可以用來跳出循環。如果代碼塊需要返回值的話,每個控制路徑都需要返回一個值或者拋出異常。

下面是一些示例:

(int x, int y) -> x + y

() -> 42

(String s) -> { System.out.println(s); }

第一個表達式有兩個整數型參數 x 和 y,表達式返回 x + y 的值。第二個表達式沒有參數直接返回一個表達式的值 42,。 第三個有一個 string 參數,使用代碼塊的方式把該參數打印出來,沒有返回值。

Lambda 示例

Runnable Lambda

來看幾個示例, 下面是一個 Runnable 的示例:

public void runnableTest() {
        System.out.println("=== RunnableTest ===");
        // 一個匿名的 Runnable
        Runnable r1 = new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello world one!");
            }
        };

        // Lambda Runnable
        Runnable r2 = () -> System.out.println("Hello world two!");

        // 執行兩個 run 函數
        r1.run();
        r2.run();
    }

這兩個實現方式都沒有參數也沒有返回值。Runnable lambda 表達式使用代碼塊的方式把五行代碼簡化為一個語句。

Comparator Lambda

在 Java 中,Comparator 接口用來排序集合。在下面的示例中一個 ArrayList 中包含了一些 Person 對象, 并依據 Person 對象的 surName 來排序。下面是 Person 類中包含的 fields:

public class Person {
    private String givenName;
    private String surName;
    private int age;
    private Gender gender;
    private String eMail;
    private String phone;
    private String address;
}

下面是分別用匿名內部類和 Lambda 表達式實現 Comparator 接口的方式:

public class ComparatorTest {
    public static void main(String[] args) {
        List<Person> personList = Person.createShortList();

        // 使用內部類實現排序
        Collections.sort(personList, new Comparator<Person>() {
            public int compare(Person p1, Person p2) {
                return p1.getSurName().compareTo(p2.getSurName());
            }
        });

        System.out.println("=== Sorted Asc SurName ===");
        for (Person p : personList) {
            p.printName();
        }

        // 使用 Lambda 表達式實現

        // 升序排列
        System.out.println("=== Sorted Asc SurName ===");
        Collections.sort(personList, (Person p1, Person p2) -> p1.getSurName().compareTo(p2.getSurName()));
        for (Person p : personList) {
            p.printName();
        }

        // 降序排列
        System.out.println("=== Sorted Desc SurName ===");
        Collections.sort(personList, (p1, p2) -> p2.getSurName().compareTo(p1.getSurName()));
        for (Person p : personList) {
            p.printName();
        }
    }
}

可以看到 匿名內部類可以通過 Lambda 表達式實現。注意 第一個 Lambda 表達式定義了參數的類型為 Person;而第二個 Lambda 表達式省略了該類型定義。Lambda 表達式支持類型推倒,如果通過上下文可以推倒出所需要的類型,則可以省略類型定義。這里由于 我們把 Lambda 表達式用在一個使用泛型定義的 Comparator 地方,編譯器可以推倒出這兩個參數類型為 Person 。

Listener 表達式

最后來看看 View 點擊事件的表達式寫法:

view.setOnClickListener( v -> Toast.makeText(MainActivity.this, "Button Clicked", Toast.LENGTH_SHORT).show() );

注意, Lambda 表達式可以當做參數傳遞。類型推倒可以在如下場景使用:

  • 變量定義
  • 賦值操作
  • 返回語句
  • 數組初始化
  • 函數或者構造函數參數
  • Lambda 表達式代碼塊中
  • 條件表達式中 ? :
  • 強制轉換表達式

使用 Lambda 表達式提升代碼

本節通過一個示例來看看 Lambda 表達式 如何提升你的代碼。Lambda 表達式可以更好的支持不要重復自己(DRY)原則并且讓代碼看起來更加簡潔易懂。

一個常見的查詢案例

編碼生涯中一個很常見的案例就是從一個集合中查找出符合要求的數據。例如有很多人,每個人都帶有很多屬性,需要從這里找出符合一些條件的人。

在本示例中,我們需要查找符合三個條件的人群:

– 司機:年齡在 16 以上的人才能成為司機

– 需要服役的人:年齡在 18到25歲的男人

– 飛行員:年齡在 23 到 65 歲的人

找到這些人后,我們可以給這些人發郵件、打電話 告訴他們可以來考駕照、需要服役了等。

Person Class

Person 類代表每個人,該類具有如下屬性:

package com.example.lambda;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;

/**
 * @author MikeW
 */
public class Person {
  private String givenName;
  private String surName;
  private int age;
  private Gender gender;
  private String eMail;
  private String phone;
  private String address;

  public static class Builder{

    private String givenName="";
    private String surName="";
    private int age = 0;
    private Gender gender = Gender.FEMALE;
    private String eMail = "";
    private String phone = "";
    private String address = "";

    public Builder givenName(String givenName){
      this.givenName = givenName;
      return this;
    }

    public Builder surName(String surName){
      this.surName = surName;
      return this;
    }

    public Builder age (int val){
      age = val;
      return this;
    }

    public Builder gender(Gender val){
      gender = val;
      return this;
    }

    public Builder email(String val){
      eMail = val;
      return this;
    }

    public Builder phoneNumber(String val){
      phone = val;
      return this;
    }

    public Builder address(String val){
      address = val;
      return this;
    }

    public Person build(){
      return new Person(this);
    }
  }

  private Person(){
    super();
  }

  private Person(Builder builder){
    givenName = builder.givenName;
    surName = builder.surName;
    age = builder.age;
    gender = builder.gender;
    eMail = builder.eMail;
    phone = builder.phone;
    address = builder.address;

  }

  public String getGivenName(){
    return givenName;
  }

  public String getSurName(){
    return surName;
  }

  public int getAge(){
    return age;
  }

  public Gender getGender(){
    return gender;
  }

  public String getEmail(){
    return eMail;
  }

  public String getPhone(){
    return phone;
  }

  public String getAddress(){
    return address;
  }

  public void print(){
    System.out.println(
      "\nName: " + givenName + " " + surName + "\n" + 
      "Age: " + age + "\n" +
      "Gender: " + gender + "\n" + 
      "eMail: " + eMail + "\n" + 
      "Phone: " + phone + "\n" +
      "Address: " + address + "\n"
                );
  } 

  @Override
  public String toString(){
    return "Name: " + givenName + " " + surName + "\n" + "Age: " + age + "  Gender: " + gender + "\n" + "eMail: " + eMail + "\n";
  } 

  public static List<Person> createShortList(){
    List<Person> people = new ArrayList<>();

    people.add(
      new Builder()
            .givenName("Bob")
            .surName("Baker")
            .age(21)
            .gender(Gender.MALE)
            .email("bob.baker@example.com")
            .phoneNumber("201-121-4678")
            .address("44 4th St, Smallville, KS 12333")
            .build() 
      );

    people.add(
      new Builder()
            .givenName("Jane")
            .surName("Doe")
            .age(25)
            .gender(Gender.FEMALE)
            .email("jane.doe@example.com")
            .phoneNumber("202-123-4678")
            .address("33 3rd St, Smallville, KS 12333")
            .build() 
      );

    people.add(
      new Builder()
            .givenName("John")
            .surName("Doe")
            .age(25)
            .gender(Gender.MALE)
            .email("john.doe@example.com")
            .phoneNumber("202-123-4678")
            .address("33 3rd St, Smallville, KS 12333")
            .build()
    );

    people.add(
      new Builder()
            .givenName("James")
            .surName("Johnson")
            .age(45)
            .gender(Gender.MALE)
            .email("james.johnson@example.com")
            .phoneNumber("333-456-1233")
            .address("201 2nd St, New York, NY 12111")
            .build()
    );

    people.add(
      new Builder()
            .givenName("Joe")
            .surName("Bailey")
            .age(67)
            .gender(Gender.MALE)
            .email("joebob.bailey@example.com")
            .phoneNumber("112-111-1111")
            .address("111 1st St, Town, CA 11111")
            .build()
    );

    people.add(
      new Builder()
            .givenName("Phil")
            .surName("Smith")
            .age(55)
            .gender(Gender.MALE)
            .email("phil.smith@examp;e.com")
            .phoneNumber("222-33-1234")
            .address("22 2nd St, New Park, CO 222333")
            .build()
    );

    people.add(
      new Builder()
            .givenName("Betty")
            .surName("Jones")
            .age(85)
            .gender(Gender.FEMALE)
            .email("betty.jones@example.com")
            .phoneNumber("211-33-1234")
            .address("22 4th St, New Park, CO 222333")
            .build()
    );


    return people;
  }

}

Person 類使用一個 Builder 來創建新的對象。 通過 createShortList 函數來創建一些模擬數據。

常見的實現方式

有 Person 類和搜索的條件了,現在可以撰寫一個 RoboContact 類來搜索符合條件的人了:

public class RoboContactMethods {

  public void callDrivers(List<Person> pl){
    for(Person p:pl){
      if (p.getAge() >= 16){
        roboCall(p);
      }
    }
  }

  public void emailDraftees(List<Person> pl){
    for(Person p:pl){
      if (p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE){
        roboEmail(p);
      }
    }
  }

  public void mailPilots(List<Person> pl){
    for(Person p:pl){
      if (p.getAge() >= 23 && p.getAge() <= 65){
        roboMail(p);
      }
    }
  }


  public void roboCall(Person p){
    System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone());
  }

  public void roboEmail(Person p){
    System.out.println("EMailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getEmail());
  }

  public void roboMail(Person p){
    System.out.println("Mailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getAddress());
  }

}

這里分別定義了 callDrivers、 emailDraftees 和 mailPilots 三個函數,每個函數的名字都表明了他們實現的功能。在每個函數中都包含了搜索的條件,但是這個實現由一些問題:

  • 沒有遵守 DRY 原則
    • 每個函數都重復了一個循環操作
    • 每個函數都需要重新寫一次查詢條件
  • 每個搜索場景都需要很多代碼來實現
  • 代碼沒有靈活性。如果搜索條件改變了,需要修改代碼的多個地方來符合新的需求。并且代碼也不好維護。

重構這些函數

如何改進這些問題呢?如果把搜索條件判斷提取出來,放到單獨的地方是個不錯的想法。

public class RoboContactMethods2 {

  public void callDrivers(List<Person> pl){
    for(Person p:pl){
      if (isDriver(p)){
        roboCall(p);
      }
    }
  }

  public void emailDraftees(List<Person> pl){
    for(Person p:pl){
      if (isDraftee(p)){
        roboEmail(p);
      }
    }
  }

  public void mailPilots(List<Person> pl){
    for(Person p:pl){
      if (isPilot(p)){
        roboMail(p);
      }
    }
  }

  public boolean isDriver(Person p){
    return p.getAge() >= 16;
  }

  public boolean isDraftee(Person p){
    return p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE;
  }

  public boolean isPilot(Person p){
    return p.getAge() >= 23 && p.getAge() <= 65;
  }

  public void roboCall(Person p){
    System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone());
  }

  public void roboEmail(Person p){
    System.out.println("EMailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getEmail());
  }

  public void roboMail(Person p){
    System.out.println("Mailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getAddress());
  }

}

搜索條件判斷封裝到一個函數中了,比第一步的實現有點改進。搜索測試條件可以重用,但是這里還是有一些重復的代碼并且每個搜索用例還是需要一個額外的函數。是否有更好的方法把搜索條件傳遞給函數?

匿名類

在 lambda 表達式出現之前,匿名內部類是一種選擇。例如,我們可以定義個 MyTest 接口,里面有個 test 函數,該函數有個參數 t 然后返回一個 boolean 值告訴該 t 是否符合條件。該接口定義如下:

public interface MyTest<T> {
  public boolean test(T t);
}

使用該接口的實現搜索功能的改進代碼如下:

public class RoboContactAnon {

  public void phoneContacts(List<Person> pl, MyTest<Person> aTest){
    for(Person p:pl){
      if (aTest.test(p)){
        roboCall(p);
      }
    }
  }

  public void emailContacts(List<Person> pl, MyTest<Person> aTest){
    for(Person p:pl){
      if (aTest.test(p)){
        roboEmail(p);
      }
    }
  }

  public void mailContacts(List<Person> pl, MyTest<Person> aTest){
    for(Person p:pl){
      if (aTest.test(p)){
        roboMail(p);
      }
    }
  }  

  public void roboCall(Person p){
    System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone());
  }

  public void roboEmail(Person p){
    System.out.println("EMailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getEmail());
  }

  public void roboMail(Person p){
    System.out.println("Mailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getAddress());
  }

}

這比之前的代碼又改進了一步,現在只需要執行 3個函數就可以實現搜索功能了。但是調用這些代碼需要使用匿名內部類,這樣調用的代碼看起來非常丑:

public class RoboCallTest03 {

  public static void main(String[] args) {

    List<Person> pl = Person.createShortList();
    RoboContactAnon robo = new RoboContactAnon();

    System.out.println("\n==== Test 03 ====");
    System.out.println("\n=== Calling all Drivers ===");
    robo.phoneContacts(pl, 
        new MyTest<Person>(){
          @Override
          public boolean test(Person p){
            return p.getAge() >=16;
          }
        }
    );

    System.out.println("\n=== Emailing all Draftees ===");
    robo.emailContacts(pl, 
        new MyTest<Person>(){
          @Override
          public boolean test(Person p){
            return p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE;
          }
        }
    );


    System.out.println("\n=== Mail all Pilots ===");
    robo.mailContacts(pl, 
        new MyTest<Person>(){
          @Override
          public boolean test(Person p){
            return p.getAge() >= 23 && p.getAge() <= 65;
          }
        }
    );


  }
}

這就是大家深惡痛絕的匿名內部類嵌套問題,五行代碼中只有一行是真正有用的代碼,但是其他四行模板代碼每次都要重新來一遍。

Lambda 表達式派上用場了

Lambda 表達式可以完美的解決該問題。前面我們已經看到了 Lambda 表達式如何解決 OnClickListener 問題的了。

在看看這里 Lambda 表達式如何實現的之前,我們先來看看 Java 8 中的一個新包: /java

在上一個示例中,MyTest functional interface 作為函數的參數。但是如果每次都需要我們自己自定義一個這樣的接口是不是比較繁瑣呢? 所以 Java 8 提供了這個 java.util.function 包,里面定義了幾十個常用的 functional interface。這里 Predicate 這個接口符合我們的要求:

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

test 函數需要一個泛型的參數然后返回一個布爾值。過濾一個對象就需要這樣的操作。下面是如何用 Lambda 表達式實現搜索的代碼:

public class RoboContactLambda {
  public void phoneContacts(List<Person> pl, Predicate<Person> pred){
    for(Person p:pl){
      if (pred.test(p)){
        roboCall(p);
      }
    }
  }

  public void emailContacts(List<Person> pl, Predicate<Person> pred){
    for(Person p:pl){
      if (pred.test(p)){
        roboEmail(p);
      }
    }
  }

  public void mailContacts(List<Person> pl, Predicate<Person> pred){
    for(Person p:pl){
      if (pred.test(p)){
        roboMail(p);
      }
    }
  }  

  public void roboCall(Person p){
    System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone());
  }

  public void roboEmail(Person p){
    System.out.println("EMailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getEmail());
  }

  public void roboMail(Person p){
    System.out.println("Mailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getAddress());
  }

}

這樣使用 Lambda 表達式就解決了這個匿名內部類的問題,下面是使用 Lambda 表達式來調用這些搜索函數的代碼:

public class RoboCallTest04 {

  public static void main(String[] args){ 

    List<Person> pl = Person.createShortList();
    RoboContactLambda robo = new RoboContactLambda();

    // Predicates
    Predicate<Person> allDrivers = p -> p.getAge() >= 16;
    Predicate<Person> allDraftees = p -> p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE;
    Predicate<Person> allPilots = p -> p.getAge() >= 23 && p.getAge() <= 65;

    System.out.println("\n==== Test 04 ====");
    System.out.println("\n=== Calling all Drivers ===");
    robo.phoneContacts(pl, allDrivers);

    System.out.println("\n=== Emailing all Draftees ===");
    robo.emailContacts(pl, allDraftees);

    System.out.println("\n=== Mail all Pilots ===");
    robo.mailContacts(pl, allPilots);

    // Mix and match becomes easy
    System.out.println("\n=== Mail all Draftees ===");
    robo.mailContacts(pl, allDraftees);  

    System.out.println("\n=== Call all Pilots ===");
    robo.phoneContacts(pl, allPilots);    

  }
}

上面的示例代碼可以在這里下載: RoboCallExample.zip

java.util.function 包

該包包含了很多常用的接口,比如:

– Predicate: 判斷是否符合某個條件

– Consumer: 使用參數對象來執行一些操作

– Function: 把對象 T 變成 U

– Supplier:提供一個對象 T (和工廠方法類似)

– UnaryOperator: A unary operator from T -> T

– BinaryOperator: A binary operator from (T, T) -> T

可以詳細看看這個包里面都有哪些接口,然后思考下如何用 Lambda 表達式來使用這些接口。

改進人名的輸出方式

比如在上面的示例中 ,把找到的人名字給打印出來,但是不同的地方打印的格式要求不一樣,比如有些地方要求把 姓 放到 名字的前面打印出來;而有些地方要求把 名字 放到 姓 的前面打印出來。 下面來看看如何實現這個功能:

常見的實現

兩種不同打印人名的實現方式:

public void printWesternName(){

    System.out.println("\nName: " + this.getGivenName() + " " + this.getSurName() + "\n" +
             "Age: " + this.getAge() + "  " + "Gender: " + this.getGender() + "\n" +
             "EMail: " + this.getEmail() + "\n" + 
             "Phone: " + this.getPhone() + "\n" +
             "Address: " + this.getAddress());
  }


  public void printEasternName(){

    System.out.println("\nName: " + this.getSurName() + " " + this.getGivenName() + "\n" +
             "Age: " + this.getAge() + "  " + "Gender: " + this.getGender() + "\n" +
             "EMail: " + this.getEmail() + "\n" + 
             "Phone: " + this.getPhone() + "\n" +
             "Address: " + this.getAddress());
  }

Function 接口非常適合這類情況,該接口的 apply 函數是這樣定義的:

public R apply(T t){ }

參數為泛型類型 T 返回值為泛型類型 R。例如把 Person 類當做參數而 String 當做返回值。這樣可以用該函數實現一個更加靈活的打印人名的實現:

public String printCustom(Function <Person, String> f){
      return f.apply(this);
  }

很簡單,一個 Function 對象作為參數,返回一個 字符串。

下面是測試打印的程序:

public class NameTestNew {

  public static void main(String[] args) {

    System.out.println("\n==== NameTestNew ===");

    List<Person> list1 = Person.createShortList();

    // Print Custom First Name and e-mail
    System.out.println("===Custom List===");
    for (Person person:list1){
        System.out.println(
            person.printCustom(p -> "Name: " + p.getGivenName() + " EMail: " + p.getEmail())
        );
    }


    // Define Western and Eastern Lambdas

    Function<Person, String> westernStyle = p -> {
      return "\nName: " + p.getGivenName() + " " + p.getSurName() + "\n" +
             "Age: " + p.getAge() + "  " + "Gender: " + p.getGender() + "\n" +
             "EMail: " + p.getEmail() + "\n" + 
             "Phone: " + p.getPhone() + "\n" +
             "Address: " + p.getAddress();
    };

    Function<Person, String> easternStyle =  p -> "\nName: " + p.getSurName() + " " 
            + p.getGivenName() + "\n" + "Age: " + p.getAge() + "  " + 
            "Gender: " + p.getGender() + "\n" +
            "EMail: " + p.getEmail() + "\n" + 
            "Phone: " + p.getPhone() + "\n" +
            "Address: " + p.getAddress();   

    // Print Western List
    System.out.println("\n===Western List===");
    for (Person person:list1){
        System.out.println(
            person.printCustom(westernStyle)
        );
    }

    // Print Eastern List
    System.out.println("\n===Eastern List===");
    for (Person person:list1){
        System.out.println(
            person.printCustom(easternStyle)
        );
    }


  }
}

上面的示例中演示了各種使用方式。也可以把 Lambda 表達式保存到一個變量中,然后用這個變量來調用函數。

以上代碼可以在這里下載: LambdaFunctionExamples.zip

當集合遇到 Lambda 表達式

前面介紹了如何配合 Function 接口來使用 Lambda 表達式。其實 Lambda 表達式最強大的地方是配合集合使用。

在前面的示例中我們多次用到了集合。并且一些使用 Lambda 表達式 的地方也改變了我們使用集合的方式。這里我們再來介紹一些配合集合使用的高級用法。

我們可以把前面三種搜索條件封裝到一個 SearchCriteria 類中:

public class SearchCriteria {

  private final Map<String, Predicate<Person>> searchMap = new HashMap<>();

  private SearchCriteria() {
    super();
    initSearchMap();
  }

  private void initSearchMap() {
    Predicate<Person> allDrivers = p -> p.getAge() >= 16;
    Predicate<Person> allDraftees = p -> p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE;
    Predicate<Person> allPilots = p -> p.getAge() >= 23 && p.getAge() <= 65;

    searchMap.put("allDrivers", allDrivers);
    searchMap.put("allDraftees", allDraftees);
    searchMap.put("allPilots", allPilots);

  }

  public Predicate<Person> getCriteria(String PredicateName) {
    Predicate<Person> target;

    target = searchMap.get(PredicateName);

    if (target == null) {

      System.out.println("Search Criteria not found... ");
      System.exit(1);

    }

    return target;

  }

  public static SearchCriteria getInstance() {
    return new SearchCriteria();
  }
}

每個 Predicate 示例都保存在這個類中,然后可以在后面測試代碼中使用。

循環

先來看看結合中的 forEach 函數如何配合 Lambda 表達式使用:

public class Test01ForEach {

  public static void main(String[] args) {

    List<Person> pl = Person.createShortList();

    System.out.println("\n=== Western Phone List ===");
    pl.forEach( p -> p.printWesternName() );

    System.out.println("\n=== Eastern Phone List ===");
    pl.forEach(Person::printEasternName);

    System.out.println("\n=== Custom Phone List ===");
    pl.forEach(p -> { System.out.println(p.printCustom(r -> "Name: " + r.getGivenName() + " EMail: " + r.getEmail())); });

  }

}

第一個使用了標準的 Lambda 表達式,調用 Person 對象的 printWesternName 函數來打印名字。而第二個用戶則演示了如何使用 函數引用(method reference) 。如果要執行對象上的一個函數則這種函數引用的方式可以替代標準的 Lambda 的語法。最后一個演示了如何 printCustom 函數。注意查看在 Lambda 表達式里面嵌套 Lambda 表達式的時候,參數的名字是有變化的。(第一個 Lambda 表達式的參數為 p 而第二個為 r)

Chaining and Filters

除了循環迭代集合以外,還可以串聯多個函數的調用。如下所示:

public class Test02Filter {

  public static void main(String[] args) {

    List<Person> pl = Person.createShortList();

    SearchCriteria search = SearchCriteria.getInstance();

    System.out.println("\n=== Western Pilot Phone List ===");

    pl.stream().filter(search.getCriteria("allPilots"))
      .forEach(Person::printWesternName);


    System.out.println("\n=== Eastern Draftee Phone List ===");

    pl.stream().filter(search.getCriteria("allDraftees"))
      .forEach(Person::printEasternName);

  }
}

先把集合轉換為 stream 流,然后就可以串聯調用多個操作了。這里先用搜索條件過濾集合,然后在符合過濾條件的新集合上執行循環打印操作。

Getting Lazy

上面演示的功能有用,但是集合中已經有循環方法了為啥還需要添加一個新的循環的方式呢? 通過把循環迭代集合的功能實現到類庫中,Java 開發者可以做更多的代碼優化。要進一步解釋這個概念,需要先了解一些術語:

  • Laziness:在編程語言中,Laziness 代表只有當你需要處理該對象的時候才去處理他們。在上面的示例中,最后一種循環變量的方式為 lazy 的,因為通過搜索條件的對象只有 2 個留著集合中,最終的打印人名只會發生在這兩個對象上。
  • Eagerness: 在集合中的每個對象上都執行操作別稱之為 eager。例如一個 增強的 for 循環遍歷一個集合去處理里面的兩個對象,并稱之為更加 eager 。

stream 函數

前面的示例中,在過濾和循環操作之前,先調用了stream 函數。該函數把集合對象變為一個 java.util.stream.Stream 對象。在 Stream 對象上可以串聯調用各種操作。默認情況下,一個對象被處理后在 stream 中就不可用了。所以一個特定 stream 對象上的串聯操作只能執行一次。 同時 Stream 還可以是順序(默認如此)執行還可以并行執行。最后我們會演示并行執行的示例。

變化和結果(Mutation and Results)

前面已經說了, Stream 使用后就不能再次使用了。因此,在 Stream 中的對象狀態不能改變,也就是要求每個元素都是不可變的。但是,如果你想在串聯操作中返回對象該如何辦呢? 可以把結果保存到一個新的集合中。如下所示:

public class Test03toList {

  public static void main(String[] args) {

    List<Person> pl = Person.createShortList();

    SearchCriteria search = SearchCriteria.getInstance();

    // Make a new list after filtering.
    List<Person> pilotList = pl
            .stream()
            .filter(search.getCriteria("allPilots"))
            .collect(Collectors.toList());

    System.out.println("\n=== Western Pilot Phone List ===");
    pilotList.forEach(Person::printWesternName);

  }

}

上面示例中的 collect 函數把過濾的結果保存到一個新的結合中。然后我們可以遍歷這個集合。

使用 map 來計算結果

map 函數通常配合 filter 使用。該 函數使用一個對象并把他轉換為另外一個對象。下面顯示了如何通過map 來計算所有人的年齡之和。

public class Test04Map {

  public static void main(String[] args) {
    List<Person> pl = Person.createShortList();

    SearchCriteria search = SearchCriteria.getInstance();

    // Calc average age of pilots old style
    System.out.println("== Calc Old Style ==");
    int sum = 0;
    int count = 0;

    for (Person p:pl){
      if (p.getAge() >= 23 && p.getAge() <= 65 ){
        sum = sum + p.getAge();
        count++;
      }
    }

    long average = sum / count;
    System.out.println("Total Ages: " + sum);
    System.out.println("Average Age: " + average);


    // Get sum of ages
    System.out.println("\n== Calc New Style ==");
    long totalAge = pl
            .stream()
            .filter(search.getCriteria("allPilots"))
            .mapToInt(p -> p.getAge())
            .sum();

    // Get average of ages
    OptionalDouble averageAge = pl
            .parallelStream()
            .filter(search.getCriteria("allPilots"))
            .mapToDouble(p -> p.getAge())
            .average();

    System.out.println("Total Ages: " + totalAge);
    System.out.println("Average Age: " + averageAge.getAsDouble());    

  }

}

第一種使用傳統的 for 循環來計算平均年齡。第二種使用 map 把 person 對象轉換為其年齡的整數值,然后計算其總年齡和平均年齡。

在計算平均年齡時候還調用了 parallelStream 函數,所以平均年齡可以并行的計算。

上面的示例代碼可以在這里下載: LambdaCollectionExamples.zip

總結

感覺如何?是不是覺得 Lambda 表達式棒棒噠,亟不可待的想在項目中使用了吧。 神馬? 你說 Andorid 不支持 Java 8 不能用 Lambda 表達式。好吧,其實你可以使用 gradle-retrolambda 插件把 Lambda 表達式 抓換為 Java 7 版本的代碼。 還不趕緊去試試!!

來自: http://blog.chengyunfeng.com/?p=902

 本文由用戶 tmac6438 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!