天天看點

Java 8 新特性——Lambdas 表達式 下載下傳 Demo下載下傳 Demo

本文内容

  • 引入
  • 測試資料
  • collect(toList())
  • map
  • filter
  • flatMap
  • max 和 min
  • reduce
  • 整合操作
  • 參考資料

Java 8 對核心類庫的改進主要包括集合類的 API 和新引入的流(Stream)。流使得程式員得以站在更高的抽象層次上對集合進行操作。

本文主要介紹 java.util.stream 中 Lambdas 表達式的使用。

下載下傳 Demo

假設有個藝術家的清單集合,後面會給出定義(藝術家包含名字,歌曲,國籍等屬性),在此先借用一下。若計算來自 UK 的藝術家的人數,如下代碼所示:

int count = 0;      
for (Artist artist : allArtists) {      
if (artist.isFrom("UK")) {      
count++;      
}      
}      

很“簡單”、很“标準”的寫法,無非是周遊一遍,如果是來自 UK 的,計數就增 1。

這段代碼是指令式的程式設計,包含太多樣闆代碼,但樣闆代碼模糊了代碼的本意,無法流暢地表達程式員的意圖。總體來看,for 循環會将行為和方法混為一談。個人認為,行為比方法抽象層次要高,一個行為可以由多個方法構成,也就是說,一個行為可能會經由多個方法來完成。

for 循環其實是一個封裝了疊代的文法糖。如下代碼所示:

int count = 0;      
Iterator<Artist> iterator = allArtists.iterator();      
while (iterator.hasNext()) {      
Artist artist = iterator.next();      
if (artist.isFrom("UK")) {      
count++;      
}      
}      

無論如何,上面的方式都不能達到抽象的目的,無法流程地表達意圖,而且,本質上都是一種串行化的操作。

下面用 Java 8 新特性 Stream 的方式實作上面功能,如下代碼所示:

long count = allArtists.stream().filter(artist -> artist.isFrom("UK")).count();      

能夠清晰地表達意圖;如果想并行,可以在 Stream 上調用 Parallel 方法,再執行後續操作。

整個過程被分解為兩個更簡單的操作:過濾和計數,看似化簡為繁,多了一步,好像是執行了兩次循環,而事實上,類庫設計更精妙,隻需對藝術家集合疊代一次。

這也是函數式程式設計的思想,假如給定一個名稱清單,有的隻有一個字元。現要求用逗号做分割符,并傳回清單中的名稱,字元串中不包含單字母名稱,每個名稱的首字母都大寫。代碼很容易寫,關鍵在于用什麼思想。這裡本質上執行了三個任務:篩選,清單以消除單字元,将清單中每個名稱的首字母變換 為大寫,然後将清單轉化 為一個字元串。在指令式語言中,不得不為三個任務都使用同一低級機制(對清單進行疊代)。函數式語言将篩選、變換和轉化視為常見操作,是以它們提供給您從不同視角解決問題的方式。

Scala 分别為篩選、變換和轉化概念使用了行業通用的名稱,即 filter、map 和 reduce。你會下接下來的 Java 8 中看到類似的類庫。

Track、Album、Artist 類的具體定義,檢視 Demo。你可以想象,假設你有一張 CD,Album 是專輯,就是這張 CD;一個 Album 包含多個音樂,Track 就是每個音樂,其包含音樂名和時長;Artist 就是張 CD 的藝術家,他可能是個人,也可能是團隊。

package com.example.java8lambdas.data;      
import java.util.Arrays;      
import java.util.List;      
import java.util.stream.Stream;      
import com.example.java8lambdas.base.Album;      
import com.example.java8lambdas.base.Artist;      
import com.example.java8lambdas.base.Track;      
import static java.util.Arrays.asList;      
public class SampleData {      
public static final Artist johnColtrane = new Artist("John Coltrane", "US");      
public static final Artist johnLennon = new Artist("John Lennon", "UK");      
public static final Artist paulMcCartney = new Artist("Paul McCartney", "UK");      
public static final Artist georgeHarrison = new Artist("George Harrison", "UK");      
public static final Artist ringoStarr = new Artist("Ringo Starr", "UK");      
public static final List<Artist> membersOfTheBeatles = Arrays.asList(johnLennon, paulMcCartney, georgeHarrison,      
ringoStarr);      
public static final Artist theBeatles = new Artist("The Beatles", membersOfTheBeatles, "UK");      
public static final Album aLoveSupreme = new Album("A Love Supreme",      
asList(new Track("Acknowledgement", 467), new Track("Resolution", 442)), asList(johnColtrane));      
public static final Album sampleShortAlbum = new Album("sample Short Album", asList(new Track("short track", 30)),      
asList(johnColtrane));      
public static final Album manyTrackAlbum = new Album(      
"sample Short Album", asList(new Track("short track", 30), new Track("short track 2", 30),      
new Track("short track 3", 30), new Track("short track 4", 30), new Track("short track 5", 30)),      
asList(johnColtrane));      
public static Stream<Album> albums = Stream.of(aLoveSupreme);      
public static Stream<Artist> threeArtists() {      
return Stream.of(johnColtrane, johnLennon, theBeatles);      
}      
public static List<Artist> getThreeArtists() {      
return Arrays.asList(johnColtrane, johnLennon, theBeatles);      
}      
public static List<Album> getAlbum() {      
return Arrays.asList(aLoveSupreme, sampleShortAlbum, manyTrackAlbum);      
}      
}      

由Stream 裡的值生成一個清單,這是一個及早求值操作。很多 Stream 操作都是惰性求值,是以,調用 Stream 上的一系列方法後,還需要最後再調用一個類似 collect 的及早求值方法。

所謂,惰性求值方法,最終不産生新集合的方法,Stream 大部分方法都是惰性求值;而像 count 這樣最終會從 Stream 産生值的方法是及早求值方法。判斷一個操作是惰性求值還是及早求值,隻需看它的傳回值。如果傳回值是 Stream,那麼就是惰性求值;否則,就是及早求值。

Stream.of(johnColtrane, johnLennon, theBeatles).collect(toList())      

把三個藝術家變成一個 List<Artist> 集合。

将一種類型的值轉換成另一種類型,也就是,将一個流中的值轉換成一個新的流。若有一個 List<Artist> 清單 artists,則

List<String> collects = artists.stream().map(artist -> artist.getName()).collect(toList());      

傳回所有藝術家的名字。注意,傳回的是字元串清單。藝術家包含衆多屬性,但最後我需要的隻是他們的名字。

Java 8 引入了方法引用的概念,是以,上面的代碼也可以寫成,“類名::方法”的形式:

artists.stream().map(Artist::getName).collect(toList());      

map,映射,也可以完成資料類型轉換,如下面代碼,将字元串轉換成大寫;将字元串轉換成整型;将十六進制轉換成十進制:

List<String> strArr = Stream.of("a", "b", "c").map(s -> s.toUpperCase()).collect(toList());      
Assert.assertEquals(Arrays.asList("A", "B", "C"), strArr);      
List<Integer> iArr = Stream.of("1", "2", "3").map(s -> Integer.valueOf(s)).collect(toList());      
Assert.assertEquals(Arrays.asList(1, 2, 3), iArr);      
List<Integer> iArr2 = Stream.of("0A", "0B", "0C").map(s -> Integer.valueOf(s, 16)).collect(toList());      
Assert.assertEquals(Arrays.asList(10, 11, 12), iArr2);      

周遊并檢查元素。保留 Stream 中的一些元素,而過濾掉其他的。若有一個 List<Artist> 清單 artists,則

List<Artist> collect = artists.stream().filter(artist -> "UK".equals(artist.getNationality())).collect(toList());      

傳回所有國籍為 UK 的藝術家清單。

可用 Stream 替換值,将多個 Stream 連接配接成一個 Stream。若有兩個藝術家 List<Artist> 清單 artists1 和 artists2,則

List<Artist> collect = Stream.of(artists1, artists2).flatMap(numbers -> numbers.stream()).collect(toList());      

傳回兩個藝術家清單的集合。

求最大值和最小值。若有三首音樂,則

List<Track> tracks = Arrays.asList(new Track("Bakai", 524), new Track("Violets for Your Furs", 378),      
new Track("Time Was", 541));      
Track shortestTrack = tracks.stream().min(Comparator.comparing(track -> track.getLength())).get();      

傳回時長最小的音樂。

歸約,可以實作從一組值中生成一個值。Stream 的 count、min、max 方法,事實上,都是 reduce 操作。如下所示,累加:

int sum = Stream.of(1, 2, 3).reduce(0, (acc, element) -> acc + element);      

下面再看另一個例子。假如,找出某張專輯上所有樂隊的國籍。可将這個問題分解為如下步驟:

  1. 找出專輯上的所有表演者;
  2. 分辨哪些表演者是樂隊;
  3. 找出每個樂隊的國籍;
  4. 将找到的國籍放入一個集合。
Set<String> collect = album.getMusicians().filter(artist -> artist.getName().startsWith("The"))      
.map(artist -> artist.getNationality()).collect(toSet());      
說明:
  • 藝術家清單既有個人,也有樂隊,可以認為“The”開頭的是樂隊。
  • Album 類的 getMusicians 方法傳回的是 Stream 對象。這讓我們看到,暴露 Stream 集合也許要比暴露 List 或 Set 對象更好,其最大優點在于,很好地封裝了内部實作的資料結構。使用者在實際操作中無論如何使用,對不會影響到内部的 List 或 Set。

  • Java 終于有 Lambda 表達式啦~Java 8 語言變化——Lambda 表達式和接口類更改
  • Java 下一代: 函數式編碼風格——Groovy、Scala 和 Clojure 共享的函數結構及其優勢