Java 8 對(duì)核心類庫的改進(jìn)主要包括集合類的 API 和新引入的流 (Stream)。流使程序員得以站在更高的抽象層次上對(duì)集合進(jìn)行操作.
1.從外部迭代到內(nèi)部迭代
使用 for 循環(huán)計(jì)算來自倫敦的藝術(shù)家人數(shù):
int count = 0;
for (Artist artist : allArtists) {
if (artist.isFrom("London")) {
count++;
}
}
改進(jìn)一(外部迭代), 使用迭代器計(jì)算來自倫敦的藝術(shù)家人數(shù):
int count = 0;
Iterator<Artist> iterator = allArtists.iterator();
while(iterator.hasNext()) {
Artist artist = iterator.next();
if (artist.isFrom("London")) {
count++;
}
}
改進(jìn)二(內(nèi)部迭代), 利用stream計(jì)算來自倫敦的藝術(shù)家人數(shù):
long count = allArtists.stream()
.filter(artist -> artist.isFrom("London"))
.count();
Stream 是用函數(shù)式編程方式在集合類上進(jìn)行復(fù)雜操作的工具.
上述代碼可被分解為兩步更簡單的操作:
- 找出所有來自倫敦的藝術(shù)家;
- 計(jì)算他們的人數(shù).
每種操作都對(duì)應(yīng) Stream 接口的一個(gè)方法. 為了找出來自倫敦的藝術(shù)家, 需要對(duì) Stream 對(duì)象進(jìn)行過濾:filter. 過濾在這里是指"只保留通過某項(xiàng)測(cè)試的對(duì)象". 測(cè)試由一個(gè)函數(shù)完成, 根據(jù)藝術(shù)家是否來自倫敦, 該函數(shù)返回true或者false. 由于Stream API的函數(shù)式編程風(fēng)格, 我們并沒有改變集合的內(nèi)容, 而是描述出 Stream 里的內(nèi)容. count() 方法計(jì)算給定 Stream 里包含多少個(gè)對(duì)象.
2.實(shí)現(xiàn)機(jī)制
上述整個(gè)過程被分解為兩種更簡單的操作: 過濾和計(jì)數(shù), 看似有化簡為繁之嫌--兩次操作(filter和count)是否需要兩次循環(huán)? 事實(shí)上類庫設(shè)計(jì)精妙, 只需對(duì)藝術(shù)家列表迭代一次.
通常, 在 Java 中調(diào)用一個(gè)方法,計(jì)算機(jī)會(huì)隨即執(zhí)行操作:比如, System.out.println ("Hello World"); 會(huì)在終端上輸出一條信息. 這種方式叫做及早求值. 而Stream對(duì)象則則使用了另一個(gè)概念叫做惰性求值, 直到真正需要時(shí)才進(jìn)行計(jì)算.
//只過濾不計(jì)數(shù)
allArtists.stream()
.filter(artist -> artist.isFrom("London"));
這行代碼并未做什么實(shí)際性的工作, filter 只刻畫出了 Stream, 但沒有產(chǎn)生新的集合.
判斷一個(gè)操作是惰性求值還是及早求值很簡單, 只需看它的返回值: 如果返回值是 Stream, 那么是惰性求值; 如果返回值是另一個(gè)值或?yàn)榭? 那么就是及早求值.
3.常用的流操作
3.1 collect(toList())
collect(toList()) 方法由 Stream 里的值生成一個(gè)列表,是一個(gè)及早求值操作.
List<String> collected = Stream.of("a", "b", "c").collect(Collectors.toList());
assertEquals(Arrays.asList("a", "b", "c"), collected);
3.2 map
如果有一個(gè)函數(shù)可以將一種類型的值轉(zhuǎn)換成另外一種類型, map 操作就可以使用該函數(shù), 將一個(gè)流中的值轉(zhuǎn)換成一個(gè)新的流.
//使用 map 操作將字符串轉(zhuǎn)換為大寫形式
List<String> collected = Stream.of("a", "b", "hello")
.map(string -> string.toUpperCase())
.collect(Collectors.toList());
assertEquals(Arrays.asList("A", "B", "HELLO"), collected);
3.3 filter
遍歷數(shù)據(jù)并檢查其中的元素時(shí),可嘗試使用 Stream 中提供的新方法 filter.
List<String> beginningWithNumbers
= Stream.of("a", "1abc", "abc1")
.filter(value -> Character.isDigit(value.charAt(0)))
.collect(Collectors.toList());
assertEquals(Arrays.asList("1abc"), beginningWithNumbers);
filter 接受一個(gè)函數(shù)作為參數(shù), 該函數(shù)用 Lambda 表達(dá)式表示, 其返回值必須是 true 或者 false, 該 Lambda 表達(dá)式的函數(shù)接口正是之前介紹過的 Predicate.
3.4 flatMap
flatMap 方法可用 Stream 替換值,然后將多個(gè) Stream 連接成一個(gè) Stream.
//假設(shè)有一個(gè)包含多個(gè)列表的流, 現(xiàn)在希望得到所有數(shù)字的序列.
List<Integer> together = Stream.of(Arrays.asList(1, 2), Arrays.asList(3, 4))
.flatMap(numbers -> numbers.stream())
.collect(Collectors.toList());
assertEquals(Arrays.asList(1, 2, 3, 4), together);
3.5 max和min
Stream上常用的操作之一是求最大值和最小值. Stream API中的max和min操作足以解決這一問題.
//查找長度最小的元素
List<String> tracks = Arrays.asList("Bakai", "Violets for Your Furs","Time Was");
String shortestTrack = tracks.stream()
.min(Comparator.comparing(track -> track.length()))
.get();
assertEquals(tracks.get(1), shortestTrack);
3.6 reduce
reduce 操作可以實(shí)現(xiàn)從一組值中生成一個(gè)值. 在上述例子中用到的 count、min 和 max 方 法, 因?yàn)槌S枚患{入標(biāo)準(zhǔn)庫中. 事實(shí)上, 這些方法都是 reduce 操作.
下例展示這一過程, Lambda 表達(dá)式就是 reducer, 它執(zhí)行求和操作, 有兩個(gè)參數(shù): 傳入 Stream 中的當(dāng)前元素和 acc. 將兩個(gè)參數(shù)相加, acc 是累加器, 保存著當(dāng)前的累加結(jié)果.
//使用 reduce 求和
int count = Stream.of(1, 2, 3)
.reduce(0, (acc, element) -> acc + element);
assertEquals(6, count);
4.重構(gòu)遺留代碼
為了進(jìn)一步闡釋如何重構(gòu)遺留代碼, 本節(jié)將舉例說明如何將一段使用循環(huán)進(jìn)行集合操作的代碼, 重構(gòu)成基于 Stream 的操作.
下面是跟專輯相關(guān)的一組基礎(chǔ)類:
package com.chyun.java8.lambda.base;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
import static java.util.stream.Collectors.toList;
/**
* Domain class for a popular music artist.
*
* @author Richard Warburton
*/
public final class Artist {
private String name;
private List<Artist> members;
private String nationality;
public Artist(String name, String nationality) {
this(name, Collections.emptyList(), nationality);
}
public Artist(String name, List<Artist> members, String nationality) {
Objects.requireNonNull(name);
Objects.requireNonNull(members);
Objects.requireNonNull(nationality);
this.name = name;
this.members = new ArrayList<>(members);
this.nationality = nationality;
}
/**
* @return the name
*/
public String getName() {
return name;
}
/**
* @return the members
*/
public Stream<Artist> getMembers() {
return members.stream();
}
/**
* @return the nationality
*/
public String getNationality() {
return nationality;
}
public boolean isSolo() {
return members.isEmpty();
}
public boolean isFrom(String nationality) {
return this.nationality.equals(nationality);
}
@Override
public String toString() {
return getName();
}
public Artist copy() {
List<Artist> members = getMembers().map(Artist::copy).collect(toList());
return new Artist(name, members, nationality);
}
}
package com.chyun.java8.lambda.base;
import java.util.stream.Stream;
import static java.util.stream.Stream.concat;
public interface Performance {
public String getName();
public Stream<Artist> getMusicians();
// TODO: test
public default Stream<Artist> getAllMusicians() {
return getMusicians().flatMap(artist -> {
return concat(Stream.of(artist), artist.getMembers());
});
}
}
package com.chyun.java8.lambda.base;
public final class Track {
private final String name;
private final int length;
public Track(String name, int length) {
this.name = name;
this.length = length;
}
/**
* @return the name
*/
public String getName() {
return name;
}
/**
* @return the length of the track in milliseconds.
*/
public int getLength() {
return length;
}
public Track copy() {
return new Track(name, length);
}
}
package com.chyun.java8.lambda.base;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
import static java.util.Collections.unmodifiableList;
import static java.util.stream.Collectors.toList;
/**
*
* @author richard
*/
public final class Album implements Performance {
private String name;
private List<Track> tracks;
private List<Artist> musicians;
public Album(String name, List<Track> tracks, List<Artist> musicians) {
Objects.requireNonNull(name);
Objects.requireNonNull(tracks);
Objects.requireNonNull(musicians);
this.name = name;
this.tracks = new ArrayList<>(tracks);
this.musicians = new ArrayList<>(musicians);
}
/**
* @return the name
*/
public String getName() {
return name;
}
/**
* @return the tracks
*/
public Stream<Track> getTracks() {
return tracks.stream();
}
/**
* Used in imperative code examples that need to iterate over a list
*/
public List<Track> getTrackList() {
return unmodifiableList(tracks);
}
/**
* @return the musicians
*/
public Stream<Artist> getMusicians() {
return musicians.stream();
}
/**
* Used in imperative code examples that need to iterate over a list
*/
public List<Artist> getMusicianList() {
return unmodifiableList(musicians);
}
public Artist getMainMusician() {
return musicians.get(0);
}
public Album copy() {
List<Track> tracks = getTracks().map(Track::copy).collect(toList());
List<Artist> musicians = getMusicians().map(Artist::copy).collect(toList());
return new Album(name, tracks, musicians);
}
}
假定選定一組專輯, 找出其中所有長度大于 1 分鐘的曲目名稱.
//遺留代碼, 使用for循環(huán)
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;
}
第一步要修改的是 for 循環(huán). 首先使用 Stream 的 forEach 方法替換掉 for 循環(huán).
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;
}
第二步, 將內(nèi)部的 forEach 方法用Stream代替.
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;
}
第三步,用flatMap替換第一個(gè)foreach.
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;
}
第四步, 用collect()方法替換最后的forEach.
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());
}
5.練習(xí)
a.編寫一個(gè)求和函數(shù), 計(jì)算流中所有數(shù)之和;
public int addUp(Stream<Integer> numbers) {
return numbers.reduce(0, (acc, x) -> acc + x);
}
b.編寫一個(gè)函數(shù), 接受藝術(shù)家列表作為參數(shù), 返回一個(gè)字符串列表, 其中包含藝術(shù)家的姓名和國籍;
public static List<String> getNamesAndOrigins(List<Artist> artists) {
return artists.stream().flatMap(artist -> Stream.of(artist.getName(), artist.getNationality()))
.collect(Collectors.toList());
}
c.修改如下代碼,將外部迭代轉(zhuǎn)換成內(nèi)部迭代;
int totalMembers = 0;
for (Artist artist : artists) {
Stream<Artist> members = artist.getMembers();
totalMembers += members.count();
}
artists.stream().map(artist -> artist.getMembers().count()).reduce(0L, Long::sum).intValue();
d.在一個(gè)字符串列表中, 找出包含最多小寫字母的字符串.
public static Optional<String> mostLowercaseString(List<String> strings) {
return strings.stream().max(Comparator.comparing(string -> (int) string.chars().filter(Character::isLowerCase).count()));
}
e.只用 reduce 和 Lambda 表達(dá)式寫出實(shí)現(xiàn) Stream 上的 map 操作的代碼, 如果不想返回 Stream, 可以返回一個(gè) List.
public static <I, O> List<O> map(Stream<I> stream, Function<I, O> mapper) {
return stream.reduce(new ArrayList<O>(), (acc, x) -> {
List<O> newAcc = new ArrayList<>(acc);
newAcc.add(mapper.apply(x));
return newAcc;
}, (List<O> left, List<O> right) -> {
List<O> newleft = new ArrayList<>(left);
newleft.addAll(right);
return newleft;
});
}
此處關(guān)于reduce可以參考https://segmentfault.com/q/1010000004944450.
f.只用 reduce 和 Lambda 表達(dá)式寫出實(shí)現(xiàn) Stream 上的 filter 操作的代碼, 如果不想返回 Stream, 可以返回一個(gè) List.
public static <O> List<O> filter(Stream<O> stream, Predicate<O> mapper) {
return stream.reduce(new ArrayList<O>(), (acc, x) -> {
if (mapper.test(x)) {
List<O> newAcc = new ArrayList<>(acc);
newAcc.add(x);
return newAcc;
}
return acc;
}, (List<O> left, List<O> right) -> {
List<O> newleft = new ArrayList<>(left);
newleft.addAll(right);
return newleft;
});
}