本文是我在學(xué)習(xí)Java8的時候參考下面大佬的文章拷貝的,僅用于個人整理和學(xué)習(xí)用途
原文地址:[http://www.reibang.com/p/e429c517e9cb]
本篇文章我們繼續(xù)講解流的其他操作
沒有看過上篇文章的可以先點擊進去學(xué)習(xí)一下 Java8 stream流操作(上)
本篇文章主要內(nèi)容:
一種特化形式的流——數(shù)值流
- Optional 類
- 如何構(gòu)建一個流
- collect 方法
- 并行流相關(guān)問題
一. 數(shù)值流
前面介紹的如
int sum = list.stream().map(Person::getAge).reduce(0, Integer::sum); 計算元素總和的方法其中暗含了裝箱成本莫湘,map(Person::getAge) 方法過后流變成了 Stream<Integer> 類型偏友,而每個 Integer 都要拆箱成一個原始類型再進行 sum 方法求和患膛,這樣大大影響了效率。
針對這個問題 Java 8 有良心地引入了數(shù)值流 IntStream, DoubleStream, LongStream,這種流中的元素都是原始數(shù)據(jù)類型涝滴,分別是 int,double胶台,long
1. 流與數(shù)值流的轉(zhuǎn)換
流轉(zhuǎn)換為數(shù)值流
- mapToInt(T -> int) : return IntStream
- mapToDouble(T -> double) : return DoubleStream
- mapToLong(T -> long) : return LongStream
//查詢表中serial_number=6的數(shù)據(jù)
List<ATag> list = aTagMapper.selectList(new QueryWrapper<ATag>().eq("serial_number",6));
LongStream longStream = list.stream().mapToLong(ATag::getId);
數(shù)值流轉(zhuǎn)換為流 boxed() 裝箱
Stream<Long> boxed = list.stream().mapToLong(ATag::getId).boxed();
流轉(zhuǎn)換為集合
List<Long> collect = list.stream().mapToLong(ATag::getId).boxed().collect(toList());
- mapping()
將集合中的某個字段拼接成一個新的數(shù)組
List<String> newList = list.stream().collect(mapping(ATag::getNameZh, toList()));
也可以寫成
List<String> newList2 = list.stream().map(ATag::getNameZh).collect(toList());
2. 數(shù)值流方法
- sum()
- max()
- min()
- average() 等...
int sum = list.stream().mapToInt(x -> x.intValue()).sum();
int min = list.stream().mapToInt(ATag::getSerialNumber).min().getAsInt();
3. 數(shù)值范圍
IntStream 與 LongStream 擁有 range 和 rangeClosed 方法用于數(shù)值范圍處理
- IntStream : rangeClosed(int, int) / range(int, int)
- LongStream : rangeClosed(long, long) / range(long, long)
這兩個方法的區(qū)別在于一個是閉區(qū)間歼疮,一個是半開半閉區(qū)間: - rangeClosed(1, 100) :[1, 100]
- range(1, 100) :[1, 100)
我們可以利用 IntStream.rangeClosed(1, 100) 生成 1 到 100 的數(shù)值流
IntStream range = IntStream.rangeClosed(1, 100);
int sum = range.sum();
二. Optional 類
NullPointerException 可以說是每一個 Java 程序員都非常討厭看到的一個詞,針對這個問題概作, Java 8 引入了一個新的容器類 Optional腋妙,可以代表一個值存在或不存在,這樣就不用返回容易出問題的 null讯榕。之前文章的代碼中就經(jīng)常出現(xiàn)這個類骤素,也是針對這個問題進行的改進。
Optional 類比較常用的幾個方法有:
isPresent() :值存在時返回 true愚屁,反之 flase
get() :返回當(dāng)前值济竹,若值不存在會拋出異常
orElse(T) :值存在時返回該值,否則返回 T 的值
Optional 類還有三個特化版本 OptionalInt霎槐,OptionalLong送浊,OptionalDouble,剛剛講到的數(shù)值流中的 max 方法返回的類型便是這個Optional 類其中其實還有很多學(xué)問,講解它說不定也要開一篇文章,這里先講那么多丘跌,先知道基本怎么用就可以袭景。
三. 構(gòu)建流
之前我們得到一個流是通過一個原始數(shù)據(jù)源轉(zhuǎn)換而來,其實我們還可以直接構(gòu)建得到流闭树。
1. 值創(chuàng)建流
Stream.of(T...) : Stream.of("aa", "bb") 生成流
Stream<Integer> integerStream = Stream.of(1, 2, 3, 4);
- Stream.empty() : 生成空流
2. 數(shù)組與流轉(zhuǎn)換
參考文章:int []數(shù)組與List互相轉(zhuǎn)換
根據(jù)參數(shù)的數(shù)組類型創(chuàng)建對應(yīng)的流:
- Arrays.stream(T[ ])
- Arrays.stream(int[ ])
- Arrays.stream(double[ ])
- Arrays.stream(long[ ])
數(shù)組轉(zhuǎn)換為流:
int[] ints = {1, 2, 3, 4};
IntStream stream = Arrays.stream(ints);
然后運用上面學(xué)到的裝箱與轉(zhuǎn)換集合耸棒,靈活地進行數(shù)組 --> 數(shù)值流 -->數(shù)組 --> 集合之間轉(zhuǎn)換
這里如果int[] 為 Integer[] 則數(shù)組轉(zhuǎn)換為集合不需要boxed()裝箱操作
int[] ints = {1, 2, 3, 4};
IntStream integerStream = Arrays.stream(ints);
Integer[] integers = integerStream.boxed().toArray(Integer[]::new);
List<Integer> list = Arrays.stream(ints).boxed().collect(toList());
輸出:輸出:
3. 文件生成流
Stream<String> stream = Files.lines(Paths.get("data.txt"));
每個元素是給定文件的其中一行
這個方法暫時不知道怎么用与殃,文件的路徑以及位置是怎樣的有大佬懂的請留言指教一下
4. 函數(shù)生成流
兩個操作可以創(chuàng)建所謂的無限流:不像從固定集合創(chuàng)建的流那樣有固定大小的流,配合limit()使用
- iterate : 依次對每個新生成的值應(yīng)用函數(shù)
- generate :接受一個函數(shù),生成一個新的值
Stream.iterate(0, n -> n + 2).limit(10).collect(toList())
Stream.generate(Math::random).limit(10).collect(toList())
輸出結(jié)果:四. collect 收集數(shù)據(jù)
coollect 方法作為終端操作碍现,接受的是一個 Collector 接口參數(shù)幅疼,能對數(shù)據(jù)進行一些收集歸總操作
最常用的方法,把流中所有元素收集到一個 List, Set 或 Collection 中(個人理解就是將流轉(zhuǎn)換為集合)
- toList
- toSet
- toCollection
- toMap
List newlist = list.stream.collect(toList());
//如果 Map 的 Key 重復(fù)了昼接,可是會報錯的哦
Map<Integer, Person> map = list.stream().collect(toMap(Person::getAge, p -> p));
2. 匯總
(1)counting
用于計算總和:
int sum = list.stream().collect(summingInt(Person::getAge));
也可以簡化為:
int sum = list.stream().mapToInt(Person::getAge).sum();
也可以寫成
int sum = list.stream().map(Person::getAge).reduce(Interger::sum).get();
推薦使用第二種
(3)averagingInt爽篷,averagingLong,averagingDouble
用于求取平均值
Double average = list.stream().collect(averagingInt(Person::getAge));
當(dāng)然也可以這樣寫
OptionalDouble average = list.stream().mapToInt(Person::getAge).average();
不過要注意的是慢睡,這兩種返回的值是不同類型的
(4)summarizingInt狼忱,summarizingLong膨疏,summarizingDouble
這三個方法比較特殊,比如 summarizingInt 會返回 IntSummaryStatistics 類型
IntSummaryStatistics intSummaryStatistics = list.stream().collect(summarizingInt(Integer::intValue));
IntSummaryStatistics intSummaryStatistics = list.stream().collect(summarizingInt(ATag::getSerialNumber));
然后使用這個IntSummaryStatistics獲取所需要的值钻弄,比如統(tǒng)計個數(shù)佃却,均值,求和窘俺,最值饲帅,等...3. 取最值
maxBy,minBy 兩個方法瘤泪,需要一個 Comparator 接口作為參數(shù)
Optional<Person> optional = list.stream().collect(maxBy(comparing(Person::getAge)));
我們也可以直接使用 max 方法獲得同樣的結(jié)果
Optional<Person> optional = list.stream().max(comparing(Person::getAge));
4.1 joining 連接字符串
也是一個比較常用的方法灶泵,對流里面的字符串元素進行連接,其底層實現(xiàn)用的是專門用于字符串連接的 StringBuilder
List<String> list = ImmutableList.of("Tom","Jack","Speike");
list.stream().collect(joining(","));
結(jié)果:Tom,Jack,Speike
list.stream().collect(joining(" and ", "Today ", " play games."));
結(jié)果:Today Tom and Jack and Speike play games.
tips:
- 當(dāng)我們使用StringJoiner(CharSequence delimiter)初始化一個StringJoiner的時候对途,這個delimiter其實是分隔符赦邻,并不是可變字符串的初始值。
- StringJoiner(CharSequence delimiter,CharSequence prefix,CharSequence suffix)的第二個和第三個參數(shù)分別是拼接后的字符串的前綴和后綴实檀。
參考文章:Java——StringJoiner
5. groupingBy 分組
groupingBy 用于將數(shù)據(jù)分組惶洲,最終返回一個 Map 類型
Map<Integer, List<Person>> map = list.stream().collect(groupingBy(Person::getAge));
例子中我們按照年齡 age 分組,每一個 Person 對象中年齡相同的歸為一組
另外可以看出膳犹,Person::getAge 決定 Map 的鍵(Integer 類型)恬吕,list 類型決定 Map 的值(List<Person> 類型)
多級分組
groupingBy 可以接受一個第二參數(shù)實現(xiàn)多級分組:
Map<Integer, Map<Date, List<ATag>>> map = list.stream().collect(groupingBy(ATag::getSerialNumber, groupingBy(ATag::getCreateTime)));
例如上面代碼先按序號分組,再按創(chuàng)建時間分組须床,當(dāng)然在時間分組后面再添加分組groupingBy(...)
其中返回的 Map 鍵為 Integer 類型铐料,值為 Map<T, List<Person>> 類型,即參數(shù)中 groupBy(...) 返回的類型
tips:
返回的Map為無序的集合豺旬,根據(jù)業(yè)務(wù)需求可以將Map轉(zhuǎn)換為有序的LinkedHashMap
LinkedHashMap<Integer, List<ATag>> map = list.stream().collect(groupingBy(ATag::getSerialNumber, LinkedHashMap::new, toList()));
按組收集數(shù)據(jù)
Map<Integer, Integer> map = list.stream().collect(groupingBy(Person::getAge, summingInt(Person::getAge)));
該例子中钠惩,我們通過年齡進行分組,然后 summingInt(Person::getAge)) 分別計算每一組的年齡總和(Integer)族阅,最終返回一個 Map<Integer, Integer>
根據(jù)這個方法篓跛,我們可以知道,前面我們寫的:
groupingBy(Person::getAge)
其實等同于:
groupingBy(Person::getAge, toList())
6. partitioningBy 分區(qū)
分區(qū)與分組的區(qū)別在于耘分,分區(qū)是按照 true 和 false 來分的举塔,因此partitioningBy 接受的參數(shù)的 lambda 也是 T -> boolean
Map<Boolean, List<Person>> map = list.stream().collect(partitioningBy(p -> p.getAge() <= 20));
結(jié)果:
打印輸出
{
false=[Person{name='mike', age=25}, Person{name='tom', age=30}],
true=[Person{name='jack', age=20}]
}
同樣地 partitioningBy 也可以添加一個收集器作為第二參數(shù)绑警,進行類似 groupBy 的多重分區(qū)等等操作求泰。