一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|JAVA教程|ASP教程|

服務器之家 - 編程語言 - JAVA教程 - java中lambda表達式簡單用例

java中lambda表達式簡單用例

2020-06-15 11:24robin JAVA教程

讓我們從最簡單的例子開始,來學習如何對一個string列表進行排序。我們首先使用Java 8之前的方法來實現

我對java中lambda表達式的看法是相當糾結的:

一個我這么想:lambda表達式降低了java程序的閱讀體驗。java程序一直不以表現力出眾,正相反使Java流行的一個因素正是它的安全和保守——即使是初學者只要注意些也能寫出健壯且容易維護的代碼來。lambda表達式對開發人員的要求相對來說高了一層,因此也增加了一些維護難度。

另一個我這么想:作為一個碼代碼的,有必要學習并接受語言的新特性。如果只是因為它的閱讀體驗差就放棄它在表現力方面的長處,那么即使是三目表達式也有人覺得理解起來困難呢。語言也是在發展的,跟不上的就自愿被丟下好了。

我不愿意被丟下。不過非讓我做出選擇的話,我的決定還是比較保守的:沒必要一定在java語言中使用lambda——它讓目前Java圈子中的很多人不習慣,會造成人力成本的上升。如果非常喜歡的話,可以考慮使用scala。

不管怎樣,我還是開始試著掌握Lambda了,畢竟工作中維護的部分代碼使用了Lambda(相信我,我會逐步把它去掉的)。學習的教程是在Oracla – Java官網的相關教程。

——————————

假設目前正在創建一個社交網絡應用。其中的一個特性是管理員可以對符合指定條件的會員執行某些操作,如發送消息。下面的表格詳細描述了這個用例:

 

Field

描述

名稱

要執行的操作

主要參與者

管理員

前提條件

管理員登錄系統

后置條件

只對符合指定標準的會員執行操作

主成功場景

1. 管理員對要執行操作的目標會員設置過濾標準;
2. 管理員選擇要執行的操作;
3. 管理員點擊提交按鈕;
4. 系統找到符合指定標準的會員;
5. 系統對符合指定標準的會員執行預先選擇的操作。

擴展

在選擇執行操作前或者點擊提交按鈕前,管理員可以選擇是否預覽符合過濾標準的會員信息。

發生頻率

一天中會發生許多次。

 

使用下面的Person類來表示社交網絡中的會員信息:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Person {
 
  public enum Sex {
    MALE, FEMALE
  }
 
  String name;
  LocalDate birthday;
  Sex gender;
  String emailAddress;
 
  public int getAge() {
    // ...
  }
 
  public void printPerson() {
    // ...
  }
}

假設所有的會員都保存在一個List<Person>實例中。

這一節我們從一個非常簡單的方法開始,然后嘗試使用局部類和匿名類進行實現,到最后會逐步深入體驗Lambda表達式的強大和高效。可以在這里找到完整的代碼。

方案一:一個個地創建查找符合指定標準的會員的方法

這是實現前面提到的案例最簡單粗糙的方案了:就是創建幾個方法、每個方法校驗一項標準(比如年齡或是性別)。下面的一段代碼校驗了年齡大于一個指定值的情況:

?
1
2
3
4
5
6
7
public static void printPersonsOlderThan(List<person> roster, int age) {
  for (Person p : roster) {
    if (p.getAge() >= age) {
      p.printPerson();
    }
  }
}

這是一種很脆弱的方案,極有可能因為一點更新就導致應用無法運行。假如我們為Person類添加了新的成員變量或者更改了標準中衡量年齡的算法,就需要重寫大量的代碼來適應這種變化。再者,這里的限制也太過僵化了,比方說我們要打印年齡小于某個指定值的成員又該怎么做呢?再添加一個新方法printPersonsYoungerThan?這顯然是一個笨方法。

方案二:創建更通用的方法

下面的方法較之printPersonsOlderThan適應性更好一些;這個方法打印了在指定年齡段內的會員信息:

?
1
2
3
4
5
6
7
8
public static void printPersonsWithinAgeRange(
    List<person> roster, int low, int high) {
  for (Person p : roster) {
    if (low <= p.getAge() && p.getAge() < high) {
      p.printPerson();
    }
  }
}

此時又有新的想法了:如果我們要打印指定性別的會員信息,或者同時符合指定性別又在指定年齡段內的會員信息該怎么辦呢?如果我們調整了Person類,添加了諸如友好度和地理位置這樣的屬性又該怎么辦呢。盡管這樣寫方法要比printPersonsYoungerThan通用性更強一些,但是如果為每一種可能的查詢都寫一個方法也會導致代碼的脆弱。倒不如把標準檢查這一塊代碼給獨立到一個新的類中。

方案三:在一個局部類中實現標準檢查

下面的方法打印了符合檢索標準的會員信息:

?
1
2
3
4
5
6
7
public static void printPersons(List<person> roster, CheckPerson tester) {
  for (Person p : roster) {
    if (tester.test(p)) {
      p.printPerson();
    }
  }
}

在程序里使用了一個CheckPerso對象tester對List參數roster中的每個實例進行校驗。如果tester.test()返回true,就會執行printPerson()方法。為了設置檢索標準,需要實現CheckPerson接口。

下面的這個類實現了CheckPerson并提供了test方法的具體實現。這個類中的test方法過濾了滿足在美國服兵役條件的會員信息:即性別為男、且年齡在18~25歲之間。

?
1
2
3
4
5
6
7
class CheckPersonEligibleForSelectiveService implements CheckPerson {
  public boolean test(Person p) {
    return p.gender == Person.Sex.MALE &&
        p.getAge() >= 18 &&
        p.getAge() <= 25;
  }
}

要使用這個類,需要創建一個實例并觸發printPersons方法:

?
1
2
printPersons(
    roster, new CheckPersonEligibleForSelectiveService());

現在的代碼看起來不那么脆弱了——我們不需要因為Person類結構的變化而重寫代碼。不過這里仍有額外的代碼:一個新定義的接口、為應用中每個搜索標準定義了一個內部類。

因為CheckPersonEligibleForSelectiveService實現了一個接口,所以可以使用匿名類,而不需要再為每種標準分別定義一個內部類。

方案四:使用匿名類實現標準檢查

下面調用的printPersons方法中的一個參數是匿名類。這個匿名類的作用和方案三中的CheckPersonEligibleForSelectiveService類一樣:都是過濾性別為男且年齡在18和25歲之間的會員。

?
1
2
3
4
5
6
7
8
9
10
printPersons(
    roster,
    new CheckPerson() {
      public boolean test(Person p) {
        return p.getGender() == Person.Sex.MALE
            && p.getAge() >= 18
            && p.getAge() <= 25;
      }
    }
);

這個方案減少了編碼量,因為不再需要為每個要執行的檢索方案創建新類。不過這樣做仍讓人有些不舒服:盡管CheckPerson接口只有一個方法,實現的匿名類仍是有些冗長笨重。此時可以使用Lambda表達式替換匿名類,下面會說下如何使用Lambda表達式替換匿名類。

方案五:使用Lambda表達式實現標準檢查

CheckPerson接口是一個函數式接口。所謂的函數式接口就是指任何只包含一個抽象方法的接口。(一個函數式接口中也可以有多個default方法或靜態方法)。既然函數式接口中只有一個抽象方法,那么在實現這個函數式接口的方法的時候可以省略掉方法的方法名。為了實現這個想法,可以使用Lambda表達式替換匿名類表達式。在下面重寫的printPersons方法中,相關的代碼做了高亮處理:

?
1
2
3
4
5
6
printPersons(
    roster,
    (Person p) -> p.getGender() == Person.Sex.MALE
        && p.getAge() >= 18
        && p.getAge() <= 25
);

在這里還可以使用一個標準的函數接口來替換CheckPerson接口,從而進一步簡化代碼。

方案六:在Lambda表達式中使用標準函數式接口

再來看一下CheckPerson接口:

?
1
2
3
interface CheckPerson {
  boolean test(Person p);
}

這是一個非常簡單的接口。因為只有一個抽象方法,所以它也是一個函數式接口。這個抽象方法只接受一個參數并返回一個boolean值。這個抽象接口太過簡單了,以至于我們會考慮是否有必要在應用中定義一個這樣的接口。此時可以考慮使用JDK定義的標準函數式接口,可以在java.util.function包下找到這些接口。

在這個例子中,我們就可以使用Predicate<T>接口來替換CheckPerson。在這個接口中有一個boolean test(T t)方法:

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

Predicate<T>接口是一個泛型接口。泛型類(或者是泛型接口)使用一對尖括號(<>)指定了一個或多個類型參數。在這個接口中只有一個類型參數。在使用具體類聲明或實例化一個泛型類時,就會獲得一個參數化類。比如說參數化類Predicate<Person>就是這樣的:

?
1
2
3
interface Predicate<person> {
  boolean test(Person t);
}

在這個參數化類中有一個與CheckPerson.boolean test(Person p)方法的參數和返回值都一致的方法。因此就可以同如下方法所演示的一樣使用Predicate<T>接口來替換CheckPerson接口:

?
1
2
3
4
5
6
7
8
public static void printPersonsWithPredicate(
    List<person> roster, Predicate<person> tester) {
  for (Person p : roster) {
    if (tester.test(p)) {
      p.printPerson();
    }
  }
}

然后使用下面的代碼就可以像方案三中一樣篩選適齡服兵役的會員了:

?
1
2
3
4
5
6
printPersonsWithPredicate(
    roster,
    p -> p.getGender() == Person.Sex.MALE
        && p.getAge() >= 18
        && p.getAge() <= 25
);

有沒有注意到,這里使用Predicate<Person>作為參數類型時并沒有顯式指定參數類型。這里并不是唯一適用lambda表達式的地方,下面的方案會介紹更多lambda表達式的用法。

方案七:在整個應用中使用lambda表達式

再來看一下printPersonsWithPredicate 方法,考慮是否可以在這里使用lambda表達式:

?
1
2
3
4
5
6
7
8
public static void printPersonsWithPredicate(
    List<person> roster, Predicate<person> tester) {
  for (Person p : roster) {
    if (tester.test(p)) {
      p.printPerson();
    }
  }
}

在這個方法中使用Predicate實例tester檢查了roster中的每個Person實例。如果Person實例符合tester中定義的檢查標準,將會觸發Person實例的printPerson方法。

除了觸發printPerson方法,滿足tester標準的Person實例還可以執行其他的方法。可以考慮使用lambda表達式指定要執行的方法(私以為這個特性很好,解決了java中方法不能作為對象傳遞的問題)。現在需要一個類似printPerson方法的lambda表達式——一個只需要一個參數且返回為void的lambda表達式。記住一點:要使用lambda表達式,需要先實現一個函數式接口。在這個例子中需要一個函數式接口,其中只包含一個抽象方法,這個抽象方法有個類型為Person的參數,且返回為void。可以看一下JDK提供的標準函數式接口Consumer<T>,它有一個抽象方法void accept(T t)正好滿足這個要求。在下面的代碼中使用一個Consumer<T>的實例調用accept方法替換了p.printPerson():

?
1
2
3
4
5
6
7
8
9
10
public static void processPersons(
    List<person> roster,
    Predicate<person> tester,
    Consumer<person> block) {
  for (Person p : roster) {
    if (tester.test(p)) {
      block.accept(p);
    }
  }
}

對應這里,可以使用如下代碼篩選適齡服兵役的會員:

?
1
2
3
4
5
6
7
processPersons(
    roster,
    p -> p.getGender() == Person.Sex.MALE
        && p.getAge() >= 18
        && p.getAge() <= 25,
    p -> p.printPerson()
);

如果我們想做的事情不只是打印會員信息,而是更多的事情,比如驗證會員身份、獲取會員聯系方式等等。此時,我們需要一個有返回值方法的函數式接口。JDK的標準函數式接口Function<T,R>就有一個這樣的方法R apply(T t)。下面的方法從參數mapper中獲取數據,并在這些數據上執行參數block指定的行為:

?
1
2
3
4
5
6
7
8
9
10
11
12
public static void processPersonsWithFunction(
    List<person> roster,
    Predicate<person> tester,
    Function<person  , string> mapper,
    Consumer<string> block) {
  for (Person p : roster) {
    if (tester.test(p)) {
      String data = mapper.apply(p);
      block.accept(data);
    }
  }
}

下面的代碼獲取了roster中適齡服兵役的所有會員的郵箱信息并打印出來:

?
1
2
3
4
5
6
7
8
processPersonsWithFunction(
    roster,
    p -> p.getGender() == Person.Sex.MALE
        && p.getAge() >= 18
        && p.getAge() <= 25,
    p -> p.getEmailAddress(),
    email -> System.out.println(email)
);

方案八:多使用泛型

再來回顧一下processPersonsWithFunction方法。下面是這個方法的泛型版本,新方法在參數類型上要求更為寬容:

?
1
2
3
4
5
6
7
8
9
10
11
12
public static <x  , Y> void processElements(
    Iterable<x> source,
    Predicate<x> tester,
    Function<x  , Y> mapper,
    Consumer<y> block) {
  for (X p : source) {
    if (tester.test(p)) {
      Y data = mapper.apply(p);
      block.accept(data);
    }
  }
}

要打印適齡服兵役的會員信息可以像下面這樣調用processElements方法:

?
1
2
3
4
5
6
7
8
processElements(
    roster,
    p -> p.getGender() == Person.Sex.MALE
        && p.getAge() >= 18
        && p.getAge() <= 25,
    p -> p.getEmailAddress(),
    email -> System.out.println(email)
);

在方法的調用過程中執行了如下行為:

從一個集合中獲取對象信息,在這個例子里是從集合實例roster中獲取Person對象信息。
過濾能夠匹配Predicate實例tester的對象。在這個例子里,Predicate對象是一個lambda表達式,它指定了過濾適齡服兵役的條件。

將過濾后的對象交給一個Function對象mapper處理,mapper會為這個對象匹配一個值。在這個例子中Function對象mapper是一個lambda表達式,它返回了每個會員的郵箱地址。

由Consumer對象block為mapper匹配的值指定一個行為。在這個例子里,Consumer對象是一個lambda表達式,它的作用是打印一個字符串,也就是Function實例mapper返回的會員郵件地址。

方案九:使用將lambda表達式作為參數的聚集操作

下面的代碼中使用了聚集操作來打印roster集合中適齡服兵役會員的郵件地址:

?
1
2
3
4
5
6
7
roster.stream()
    .filter(
        p -> p.getGender() == Person.Sex.MALE
            && p.getAge() >= 18
            && p.getAge() <= 25)
    .map(p -> p.getEmailAddress())
    .forEach(email -> System.out.println(email));

分析下如上代碼的執行過程,整理如下表:

 

行為

聚集操作

獲取對象

Stream<E> stream()

過濾匹配Predicate實例指定標準的對象

Stream<T> filter(Predicate<? super T> predicate)

通過一個Function實例獲取對象匹配的值

<R> Stream<R> map(Function<? super T,? extends R> mapper)

執行Consumer實例指定的行為

void forEach(Consumer<? super T> action)

 

表中的filter、map和forEach操作都是聚集操作。聚集操作處理的元素來自Stream,而非是直接從集合中獲取(就是因為這示例程序中調用的第一個方法是stream())。Stream是一個數據序列。和集合不同,Stream并沒有用特定的結構存儲數據。相反的,Stream從一個特定的源獲取數據,比如從集合獲取數據,通過一個pipeline。pipeline是一個Stream操作序列,在這個例子中就是filter-map-forEach。此外,聚集操作通常采用lambda表達式作為參數,這也給了我們許多自定義的空間。

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 99re在线精品视频免费 | 国产在线精品亚洲第一区香蕉 | 男人女人性生活视频 | 欧美人妖草草xxoo | 精品精品国产自在现拍 | 免费在线观看伦理片 | 欧美午夜精品 | 日韩中文字幕在线不卡 | 精品国产麻豆免费人成网站 | 久久精品成人免费看 | 国产麻豆流白浆在线观看 | 国产99久久精品一区二区 | 国产伦码精品一区二区 | 日韩国产成人精品视频人 | 天天干天天操天天碰 | 免费老外的毛片清高 | 果冻传媒天美传媒在线小视频播放 | 99久久精品免费观看区一 | 国产视频一区 | 精品国产免费久久久久久 | 成人久久18免费网站 | 午夜网| 睡男神的这件小事小说在线阅读 | 教练你好大轻点漫 | 天天爽天天操 | 四虎影院com | 国产a在线 | 99久久免费国产香蕉麻豆 | 四虎国产一区 | 精品欧美 | 免费永久观看美女视频网站网址 | 红杏网 | 日韩视频在线精品视频免费观看 | 欧美成人免费观看国产 | 亚洲欧美专区精品伊人久久 | 亚洲性综合网 | 无套暴躁白丝秘书 | 爱情岛论坛亚洲品质自拍视频 | 亚洲欧美精品久久 | 亚洲国产精品成 | 男人添女人 |