第一個問題描述:
找出某張專輯上所有樂隊的國籍。藝術家列表里既有個人,也有 樂隊涂邀。利用一點領域知識,假定一般樂隊名以定冠詞 The 開頭库继。當然這不是絕對的,但也 差不多。
找出某張專輯上所有樂隊的國籍眯分。藝術家列表里既有個人,也有 樂隊。假定一般樂隊名以定冠詞 The 開頭柒桑。
首先, 可將這個問題分解為如下幾個步驟弊决。
- 找出專輯上的所有表演者。
- 分辨出哪些表演者是樂隊魁淳。
- 找出每個樂隊的國籍飘诗。
- 將找出的國籍放入一個集合与倡。
現(xiàn)在,找出每一步對應的 Stream API 就相對容易了: - Album 類有個 getMusicians 方法,該方法返回一個 Stream 對象,包含整張專輯中所有的
表演者; - 使用 filter 方法對表演者進行過濾,只保留樂隊;
- 使用 map 方法將樂隊映射為其所屬國家;
- 使用 collect(Collectors.toList()) 方法將國籍放入一個列表。
最后,整合所有的操作,就得到如下代碼:
Set<String> origins = album.getMusicians()
.filter(artist -> artist.getName().startsWith("The"))
.map(artist -> artist.getNationality())
.collect(toSet());
通過重構將現(xiàn)有代碼改寫為Lambada形式:
問題描述:假定選定一組專輯,找出其中所有長度大于 1 分鐘的曲目名稱
//遺留代碼:
public Set<String> findLongTracks(List<Album> albums) {
Set<String> trackNames = new HashSet<>();
for (Album album : albums) {
for (Track track : album.getTrackList()) {
if (track.getLength() > 60) {
String name = track.getName();
trackNames.add(name);
}
}
}
return trackNames;
```
如果仔細閱讀上面的這段代碼,就會發(fā)現(xiàn)幾組嵌套的循環(huán)昆稿。僅通過閱讀這段代碼很難看出 它的編寫目的,那就來重構一下(使用流來重構該段代碼的方式很多,下面介紹的只是其 中一種纺座。事實上,對 Stream API 越熟悉,就越不需要細分步驟。之所以在示例中一步一步 地重構,完全是出于幫助大家學習的目的,在工作中無需這樣做)貌嫡。
第一步要修改的是 for 循環(huán)比驻。首先使用 Stream 的 forEach 方法替換掉 for 循環(huán),但還是暫 時保留原來循環(huán)體中的代碼,這是在重構時非常方便的一個技巧该溯。調(diào)用 stream 方法從專輯 列表中生成第一個 Stream,同時不要忘了在上一節(jié)已介紹過,getTracks 方法本身就返回 一個 Stream 對象岛抄。經(jīng)過第一步重構后,代碼如下所示:
```
//第一次重構:找出長度大于 1 分鐘的曲目
public Set<String> findLongTracks(List<Album> albums) {
Set<String> trackNames = new HashSet<>();
albums.stream()
.forEach(album -> {
album.getTracks()
.forEach(track -> {
if (track.getLength() > 60) {
String name = track.getName();
trackNames.add(name);
}
});
});
return trackNames;
}
```
在第一次重構中,雖然使用了流,但是并沒有充分發(fā)揮它的作用。事實上,重構后的代 碼還不如原來的代碼好——天哪!因此,是時候引入一些更符合流風格的代碼了,最內(nèi)層 的 forEach 方法正是主要突破口狈茉。
最內(nèi)層的 forEach 方法有三個功用:找出長度大于 1 分鐘的曲目,得到符合條件的曲目名 稱,將曲目名稱加入集合 Set夫椭。這就意味著需要三項 Stream 操作:找出滿足某種條件的曲 目是 filter 的功能,得到曲目名稱則可用 map 達成,終結操作可使用 forEach 方法將曲目
名稱加入一個集合。用以上三項 Stream 操作將內(nèi)部的 forEach 方法拆分后,代碼如下所示:
```
//第二次重構:找出長度大于 1 分鐘的曲目
public Set<String> findLongTracks(List<Album> albums) {
Set<String> trackNames = new HashSet<>();
albums.stream()
.forEach(album -> {
album.getTracks()
.filter(track -> track.getLength() > 60)
.map(track -> track.getName())
.forEach(name -> trackNames.add(name));
});
return trackNames;
}
```
現(xiàn)在用更符合流風格的操作替換了內(nèi)層的循環(huán),但代碼看起來還是冗長繁瑣氯庆。將各種流嵌 套起來并不理想,最好還是用干凈整潔的順序調(diào)用一些方法蹭秋。
理想的操作莫過于找到一種方法,將專輯轉(zhuǎn)化成一個曲目的 Stream。眾所周知,任何時候 想轉(zhuǎn)化或替代代碼,都該使用 map 操作堤撵。這里將使用比 map 更復雜的 flatMap 操作,把多個 Stream 合并成一個 Stream 并返回仁讨。將 forEach 方法替換成 flatMap 后,代碼如下所示:
```
//第三次重構:找出長度大于 1 分鐘的曲目
public Set<String> findLongTracks(List<Album> albums) {
Set<String> trackNames = new HashSet<>();
albums.stream()
.flatMap(album -> album.getTracks())
.filter(track -> track.getLength() > 60)
.map(track -> track.getName())
.forEach(name -> trackNames.add(name));
return trackNames;
}
```
上面的代碼中使用一組簡潔的方法調(diào)用替換掉兩個嵌套的 for 循環(huán),看起來清晰很多。然 而至此并未結束,仍需手動創(chuàng)建一個 Set 對象并將元素加入其中,但我們希望看到的是整 個計算任務由一連串的 Stream 操作完成实昨。
到目前為止,雖然還未展示轉(zhuǎn)換的方法,但已有類似的操作洞豁。就像使用 collect(Collectors. toList()) 可以將 Stream 中的值轉(zhuǎn)換成一個列表,使用 collect(Collectors.toSet()) 可以將 Stream 中的值轉(zhuǎn)換成一個集合。因此,將最后的 forEach 方法替換為 collect,并刪掉變量 trackNames,代碼如下所示:
```
//第四次重構:找出長度大于 1 分鐘的曲目
public Set<String> findLongTracks(List<Album> albums) {
return albums.stream()
.flatMap(album -> album.getTracks())
.filter(track -> track.getLength() > 60)
.map(track -> track.getName())
.collect(Collectors.toSet());
}
```
簡而言之,選取一段遺留代碼進行重構,轉(zhuǎn)換成使用流風格的代碼荒给。最初只是簡單地使用流,但沒有引入任何有用的流操作丈挟。隨后通過一系列重構,最終使代碼更符合使用流的風 格。在上述步驟中我們沒有提到一個重點,即編寫示例代碼的每一步都要進行單元測試, 保證代碼能夠正常工作志电。重構遺留代碼時,這樣做很有幫助曙咽。