Java8實(shí)戰(zhàn)之Stream
前言
在前面一個(gè)小節(jié)中,我們已經(jīng)學(xué)習(xí)了行為參數(shù)化以及Lambda表達(dá)式,通過Lambda表達(dá)式,可以使得代碼更加簡潔,尤其是當(dāng)一個(gè)方法只需要使用一次的時(shí)候,然而,如果Java8中只有Lambda表達(dá)式的話,那還是不足以讓人感到興奮的,個(gè)人感覺,Java8中最有意思,也是最方便的功能,莫過于Stream了
Stream初窺
Stream可以翻譯為流,實(shí)際上其操作也是,流操作是Java8中引入的新功能,提供了更加強(qiáng)大的數(shù)據(jù)迭代處理方式,通過流式寫法,提供了簡潔的語法,主要注意的是Stream需要配合Lambda表達(dá)式來使用,這更加體現(xiàn)了行為參數(shù)化的思想,Java8通過將既定的操作封裝好,同時(shí),將對(duì)應(yīng)的具體行為留給用戶,極大地提高了操作的效率。
Stream的出現(xiàn),可以說是用于替代傳統(tǒng)的容器操作的,在傳統(tǒng)的容器操作中,當(dāng)需要對(duì)容器中的某些元素進(jìn)行操作的時(shí)候,我們需要迭代容器,然后篩選出合適的對(duì)象,然后再將其存放到另外的容器中,從上面的描述中,可以看到,其中的很大一部分操作:迭代容器,篩選對(duì)象,重新存放基本都是固定的,而每次都進(jìn)行手動(dòng)操作,顯然是比較繁瑣的,Stream則提供了更加便捷的操作,只需要通過對(duì)應(yīng)的操作模式,然后給出對(duì)應(yīng)的條件,即可實(shí)現(xiàn)對(duì)既定元素的操作。
為了下面的操作方便,我們先構(gòu)造需要的元素
// User對(duì)象
class User {
private Integer id;
private String name;
private Integer age;
// 省略set,get,toString方法
}
// 構(gòu)造數(shù)據(jù)
public static List<User> generateUserData() {
Random random = new Random();
List<User> users = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
users.add(new User(i, "user" + i, random.nextInt(100)));
}
return users;
}
假設(shè)現(xiàn)在有一個(gè)場景,我們需要從上面的列表中選取年齡大于20歲的對(duì)象,在傳統(tǒng)的容器操作中,一般我們會(huì)這樣操作
public List<User> getUserOlderThan20() {
List<User> users = generateUserData();
List<User> result = new ArrayList<>();
for (User user : users) {
if (user.getAge() > 20 ) {
result.add(user);
}
}
return result;
}
而在Java8中,我們可以用更加簡潔的方式來實(shí)現(xiàn)上面的操作
public List<User> getUserOlderThan20() {
List<User> users = generateUserData();
List<User> result = users.stream()
.filter(user -> user.getAge() > 20)
.collect(Collectors.toList());
return result;
}
或者上面的案例看上去并沒有那么有優(yōu)勢,那么我們來看下下面的案例,根據(jù)年齡對(duì)用戶進(jìn)行分組,年齡在1-30為年輕人,31-60為中年人,60以上為老年人(例子例子,沒有實(shí)際價(jià)值)
傳統(tǒng)的操作,我們需要如下操作
public void groupUser() {
List<User> users = generateUserData();
Map<String, List<User>> userGroup = new HashMap<>();
for (User user : users) {
if (user.getAge() > 0 && user.getAge() <= 30) {
List<User> young = userGroup.get("young");
if (young == null) {
young = new ArrayList<>();
userGroup.put("young", young);
}
userGroup.get("young").add(user);
}else if (user.getAge() <= 60) {
List<User> middle = userGroup.get("middle");
if (middle == null) {
middle = new ArrayList<>();
userGroup.put("middle", middle);
}
userGroup.get("middle").add(user);
}else {
List<User> old = userGroup.get("old");
if (old == null) {
old = new ArrayList<>();
userGroup.put("old", old);
}
userGroup.get("old").add(user);
}
}
System.out.println(userGroup);
}
可以看到,上面的操作還是挺繁瑣的,而且比較容易出錯(cuò),而在Java8中,我們則可以采用如下操作
public void testStream() {
List<User> users = generateUserData();
Map<String, List<User>> result = users.stream()
.collect(Collectors.groupingBy(
user -> {
if (user.getAge() > 0 && user.getAge() <= 30) {
return "young";
} else if (user.getAge() <= 60) {
return "middle";
} else {
return "old";
}}
));
System.out.println(result);
}
可以看到,代碼量以及自描述性的對(duì)比還是挺明顯的,Stream配合Lambda表達(dá)式,可以使得之前比較繁瑣的容器操作,變得非常簡單,而且,代碼本身的自解釋性也更強(qiáng)
Stream操作
在前面我們已經(jīng)見識(shí)到了Stream本身的特點(diǎn)--流式操作以及方便性,接下來我們來詳細(xì)學(xué)習(xí)Stream的用法。
Stream的操作可以分為兩種,一種是中間操作,例如前面的filter()操作,一種是結(jié)束操作,例如前面的collect()操作,每一個(gè)中間操作,都返回一個(gè)Stream,經(jīng)過本次處理之后的Stream,結(jié)束操作則產(chǎn)生終結(jié),其結(jié)果要么是數(shù)字,要么是字符串,要么是集合等等,總之就不再是Stream,也就是說,一個(gè)Stream可以有多個(gè)中間操作,但只能有一個(gè)結(jié)束操作
中間操作
比較常用的幾種中間操作列舉如下,更多的內(nèi)容參考API即可
-
filter(),過濾操作,入?yún)?code>Predicate<? super T> predicate -
limit(),限制操作,入?yún)?code>long maxSize -
skip(),跳過操作,入?yún)?code>long n -
distinct(),去重操作,沒有入?yún)ⅲ讓邮褂玫氖?code>Set進(jìn)行去重 -
sorted(),排序操作,可以傳入自定義的比較器Comparator<? super T> comparator -
peek(),檢查操作,用于調(diào)試操作,入?yún)?code>Consumer<? super T> action -
map(),將Stream中的元素映射為其他元素,入?yún)?code>Function<? super T, ? extends R> mapper-
mapToDouble(),將Stream轉(zhuǎn)為DoubleStream,避免裝箱機(jī)制所帶來的開銷 -
mapToLong(),將Stream轉(zhuǎn)為LongStream,避免裝箱機(jī)制所帶來的開銷 -
mapToInt(),將Stream轉(zhuǎn)為IntStream,避免裝箱機(jī)制所帶來的開銷
-
-
flatMap(),將多個(gè)Stream轉(zhuǎn)為一個(gè),注意與map()的區(qū)別,入?yún)?code>Function<? super T, ? extends Stream<? extends R>> mapper
結(jié)束操作
比較常用的幾個(gè)結(jié)束操作列舉如下,更多的內(nèi)容參考API即可
-
count(),統(tǒng)計(jì)元素個(gè)數(shù) -
forEach(),對(duì)每個(gè)元素執(zhí)行操作,入?yún)?code>Consumer<? super T> action -
findFirst(),獲取第一個(gè)元素 -
findAny(),獲取任意一個(gè)元素 -
anyMatch(),檢查元素是否至少有一個(gè)匹配,入?yún)?code>Predicate<? super T> predicate -
allMatch(),檢查所有元素是否都匹配,入?yún)?code>Predicate<? super T> predicate -
collect(),將所有內(nèi)容收集起來,入?yún)?code>Collector<? super T, A, R> collector,JDK中提供了眾多的Collector的實(shí)現(xiàn),所以,基本上不用自己實(shí)現(xiàn)-
groupingBy(),將內(nèi)容進(jìn)行分組,有三個(gè)不同的版本-
groupingBy(Function<? super T, ? extends K> classifier),僅能進(jìn)行一次分組 -
groupingBy(Function<? super T, ? extends K> classifier, Collector<? super T, A, D> downstream),注意第二個(gè)參數(shù)可以是另一個(gè)Collector,也就是說,可以通過多次的復(fù)合,達(dá)到多次分組,或者分組后再進(jìn)行其他的操作 -
groupingBy(Function<? super T, ? extends K> classifier,Supplier<M> mapFactory, Collector<? super T, A, D> downstream),自己提供一個(gè)容器,而不是使用默認(rèn)的容器
-
-
counting(),等價(jià)于前面的Stream.count() -
partitioningBy()精簡版的groupingBy(),僅能支持true、false兩種分組 -
joining(),字符串連接,需要注意,如果Stream的內(nèi)容本身不是字符串流,則需要先map()操作一下,將其轉(zhuǎn)為字符串流,可以指定分隔符,前綴,后綴 -
toList(),將結(jié)果合并為List -
toSet(),將結(jié)果合并為Set -
toMap(),將結(jié)果轉(zhuǎn)為Map -
toConcurrentMap(),將結(jié)果轉(zhuǎn)為并發(fā)Map
-
-
reduce(),根據(jù)條件合并結(jié)果,可以說,上面的所有結(jié)束操作,基本上都可以通過reduce()來實(shí)現(xiàn),reduce有三個(gè)不同形式的參數(shù),當(dāng)JDK所提供的合并操作不滿足需求時(shí),可以通過reduce來實(shí)現(xiàn)自定義的合并操作T reduce(T identity, BinaryOperator<T> accumulator)Optional<T> reduce(BinaryOperator<T> accumulator)<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner)
Stream操作實(shí)例
為了更好地理解上面的內(nèi)容,我們通過幾個(gè)小例子來實(shí)際操作一下
// 打印出年齡在30歲以上的所有用戶
users.stream()
.filter(user -> user.getAge() > 30)
.forEach(System.out::println);
// 如果換成 .count(),則是統(tǒng)計(jì)用戶的個(gè)數(shù)
// 分組并且統(tǒng)計(jì)各個(gè)分組的人數(shù)
Map<String, Long> collect = users.stream()
.collect(groupingBy(user -> {
if (user.getAge() <= 30) {
return "young";
} else if (user.getAge() <= 60) {
return "middle";
} else {
return "old";
}
}, counting()));
// 分組并且去重
Map<String, Set<User>> collect = users.stream()
.collect(groupingBy(user -> {
if (user.getAge() <= 30) {
return "young";
} else if (user.getAge() <= 60) {
return "middle";
} else {
return "old";
}
}, toSet()));
關(guān)于Stream的介紹,大致就到這里了,為了更好地掌握Stream,需要在實(shí)際使用中多加練習(xí),多加研究才是
總結(jié)
本小節(jié)主要學(xué)習(xí)了Stream的內(nèi)容,通過對(duì)比Stream與傳統(tǒng)的Collection操作,可以看出,通過Stream來操作容器,代碼將變得更加簡潔,而且,其可閱讀行也更強(qiáng),出錯(cuò)的概率也會(huì)更低,畢竟不用再自己關(guān)心迭代的過程,最后,通過幾個(gè)簡單的小例子,展示了Stream中兩種不同的操作,中間操作以及結(jié)束操作,當(dāng)然,關(guān)于Stream的更多內(nèi)容,還是需要在實(shí)際使用中不斷發(fā)現(xiàn),不斷研究,加油。