概念
? ? ? ?在Java中有關(guān)流的概念有很多哟冬,比如輸入輸出流(InputStream/OutputStream)智嚷,或者在對(duì)XML文件進(jìn)行SAX解析的流咖摹,甚至于大數(shù)據(jù)的實(shí)時(shí)處理也有流( Amazon Kinesis ),在Java8版本之后甥雕,JDK又引入了一個(gè)全新的Stream API踩身,它不是前面說(shuō)的任何一種,而是對(duì)集合對(duì)象功能的增強(qiáng)社露,同時(shí)結(jié)合Java8以后引入的Lambda表達(dá)式挟阻,可以極大的簡(jiǎn)化編碼量,而且程序的可讀性也會(huì)有一個(gè)很大的提升峭弟。
? ? ? ?Stream不僅僅只是增強(qiáng)了Java中集合的操作附鸽,同時(shí)它也是非常高效的,它提供了串行和并行的兩種模式瞒瘸,現(xiàn)在的計(jì)算機(jī)都是多核的時(shí)代坷备,可以充分利用這一特點(diǎn)提高程序執(zhí)行效率,暫時(shí)不用深究串行和并行的問(wèn)題情臭,后面會(huì)介紹到省撑,只要知道,引入了Stream API之后俯在,如果需要改成并行執(zhí)行竟秫,主體代碼完全不用動(dòng),只要切換成并行模式就行跷乐,非常方便快捷肥败。
什么是Stream?
? ? ? ?前面介紹了半天,其實(shí)如果沒(méi)有用過(guò)的人馒稍,第一眼看上去皿哨,都是模糊不清的,我們可以參考現(xiàn)實(shí)生活中的場(chǎng)景纽谒,比如工廠的流水線往史,無(wú)論是自動(dòng)化還是人工化的流水線操作,流水線的作業(yè)特點(diǎn)很明顯佛舱,一般都是在不同的流水節(jié)點(diǎn)配置不同的人員或者自動(dòng)化機(jī)器。比如:在生產(chǎn)藥品的過(guò)程中挨决,如果藥片生產(chǎn)完成后请祖,一般都需要一些檢驗(yàn)以及包裝的過(guò)程,這時(shí)一般都會(huì)有一條流水作業(yè)脖祈,在這個(gè)流水作業(yè)過(guò)程中肆捕,有負(fù)責(zé)檢測(cè)的,有負(fù)責(zé)包裝的盖高,等到整個(gè)流水線全部走完慎陵,最后就會(huì)變成規(guī)格相等的成品包裝藥。
? ? ? ?而Stream API引入后喻奥,我們針對(duì)集合的操作就有點(diǎn)類似于這種流水線的作業(yè)一樣席纽,對(duì)于集合中的元素,就相當(dāng)于一顆顆藥片撞蚕,在經(jīng)歷了篩選润梯,包裝之后,變成另外一種“規(guī)格”呈現(xiàn)出來(lái)甥厦,而流在經(jīng)歷了終結(jié)操作之后纺铭,會(huì)依據(jù)源數(shù)據(jù)生成一個(gè)我們需要的最終數(shù)據(jù)結(jié)構(gòu)。當(dāng)然這里類比流水線也并不是特別貼切刀疙,但是大致的概念的上還是很像的舶赔,便于快速了解Java8中這套新引入的Stream。
? ? ? ?Stream不是集合元素谦秧,更不是數(shù)據(jù)結(jié)構(gòu)竟纳,它跟數(shù)據(jù)的存儲(chǔ)沒(méi)有任何關(guān)系,它只是一種針對(duì)數(shù)據(jù)的計(jì)算而存在的油够,可以把它看成是更高級(jí)的迭代器蚁袭。回想一下石咬,我們傳統(tǒng)的在進(jìn)行集合操作的時(shí)候揩悄,例如:過(guò)濾掉集合中的某些元素,或者對(duì)集合中的數(shù)據(jù)進(jìn)行再次加工鬼悠,一般我們都需要不停迭代集合內(nèi)部的元素删性,然后進(jìn)行條件判斷亏娜,通過(guò)條件的留下,不符合的篩掉蹬挺,這種代碼寫(xiě)出來(lái)一般都大同小異维贺,而且代碼量都很大,閱讀起來(lái)也比較費(fèi)事巴帮。Stream可以解決這些問(wèn)題溯泣,我們只要給出針對(duì)集合內(nèi)部元素需要進(jìn)行的操作,Stream 會(huì)隱式地在內(nèi)部進(jìn)行遍歷榕茧,然后進(jìn)行相應(yīng)的數(shù)據(jù)轉(zhuǎn)換垃沦。
? ? ? ?而且類似于流水線一樣,Stream是單向的用押,不可重復(fù)的肢簿,只能遍歷一次,就像流水線作業(yè)一樣蜻拨,只能一條路走到頭池充,走完之后就無(wú)法再重新走一遍了。
? ? ? ?前面說(shuō)過(guò)Stream是可以進(jìn)行并行化模式的缎讼,這點(diǎn)也是它不同于迭代器的一點(diǎn)谅畅,這是一個(gè)什么概念呢宋光?串行化操作就是對(duì)集合內(nèi)部的數(shù)據(jù)逐個(gè)讀取,但是使用并行化模式后,就會(huì)將數(shù)據(jù)分成多段睛廊,每段數(shù)據(jù)都會(huì)在不同的線程中去處理已艰,然后將結(jié)果一同輸出祟滴,這里的并行操作依賴于Java7中引入的 Fork/Join 框架(JSR166y)來(lái)拆分任務(wù)和加速處理過(guò)程刊懈。這就類似于流水作業(yè)線一樣,會(huì)有多條流水作業(yè)線同時(shí)進(jìn)行產(chǎn)品的流水線操作捷凄,可以加速處理的速度忱详。
Stream的構(gòu)成
? ? ? ?根據(jù)前面的介紹,可以了解到跺涤,Stream中有三個(gè)很重要的步驟:源數(shù)據(jù)(source)匈睁、數(shù)據(jù)轉(zhuǎn)換(transforming values)、執(zhí)行操作(operations)桶错。數(shù)據(jù)源就不需要贅述了航唆,一般就是一個(gè)集合,數(shù)據(jù)轉(zhuǎn)換其實(shí)就是對(duì)集合中的數(shù)據(jù)進(jìn)行一系列的校驗(yàn)院刁,篩選甚至是再加工處理糯钙;執(zhí)行操作就是將符合條件的數(shù)據(jù)整合成需要的數(shù)據(jù)結(jié)構(gòu)返回出來(lái)。而且Stream的很多方法都是返回它自身(this),在編碼時(shí)完全可以采用鏈?zhǔn)骄幊倘伟叮瑢?duì)數(shù)據(jù)的操作就會(huì)像一個(gè)鏈條一樣排列在一起再榄,形成一個(gè)管道。這樣還有個(gè)好處:如果有需要享潜,可以在鏈條之中按照需要插入各個(gè)轉(zhuǎn)換操作困鸥,做成一種類似與“可插拔”的效果。
source生成方式
-
Collection和數(shù)組
Collection.stream和parallelStream方法
Arrays.stream(T array)和Stream.of()
-
從BufferReader中生成
- java.io.BufferReader.lines()
-
靜態(tài)工廠
java.util.stream.IntStream.range()
java.nio.file.Files.walk()
-
自己構(gòu)建
- java.util.Spliterator
-
其他方式
Random.ints()
BitSet.stream()
Pattern.splitAsStream(java.lang.CharSequence)
JarFile.stream()
Stream操作類型
? ? ? ?流的操作分為兩個(gè)階段剑按,也就是它的兩種類型:Intermediate和terminal疾就,簡(jiǎn)單翻譯過(guò)來(lái)就是“中間”和“最終操作”。結(jié)合前面的介紹 艺蝴,簡(jiǎn)單來(lái)說(shuō)就是:Intermediate操作對(duì)應(yīng)著數(shù)據(jù)轉(zhuǎn)換過(guò)程虐译,一個(gè)Stream可以進(jìn)行多次的Intermediate操作,而且Stream有一個(gè)特點(diǎn)吴趴,就是它的惰性(lazy),具體就體現(xiàn)在:多次的Intermediate操作實(shí)際上并不會(huì)真正遍歷數(shù)據(jù)侮攀,只有在最終的那次Terminal操作后锣枝,才會(huì)循環(huán)Stream里面的集合,然后執(zhí)行所有的操作兰英。所以它前面的Intermediate并不會(huì)真正操作數(shù)據(jù)撇叁。
除此之外,還有一種成為short-circuiting操作畦贸,翻譯過(guò)來(lái)叫短路操作:
對(duì)于一個(gè) intermediate 操作陨闹,如果它接受的是一個(gè)無(wú)限大(infinite/unbounded)的 Stream,但返回一個(gè)有限的新Stream
對(duì)于一個(gè) terminal 操作薄坏,如果它接受的是一個(gè)無(wú)限大的 Stream趋厉,但能在有限的時(shí)間計(jì)算出結(jié)果。
上面的概念看上去也是一臉懵逼胶坠,但是畢竟它是專業(yè)性解釋君账,還是要貼出來(lái)的,通俗點(diǎn)來(lái)說(shuō):
? ? ? ?我們可以聯(lián)想一種場(chǎng)景沈善,比如現(xiàn)在有無(wú)窮的人員信息數(shù)據(jù)乡数,我們現(xiàn)在需要找到其中5個(gè)具有某些特征人員信息,這時(shí)候其實(shí)我們是沒(méi)有必要全部遍歷所有的數(shù)據(jù)闻牡,只是在遍歷過(guò)程中如果發(fā)現(xiàn)符合條件的人净赴,就通過(guò),一旦達(dá)到五條數(shù)據(jù)之后罩润,剩下的就完全可以拋棄了玖翅,這個(gè)有點(diǎn)類似于我們常說(shuō)的邏輯運(yùn)算符(&& 和 || )的短路操作,如:&&操作,如果前面為false烧栋,后面就不會(huì)執(zhí)行了直接返回false写妥, || 也是一樣,如果前面一個(gè)結(jié)果為true审姓,后面也不會(huì)執(zhí)行珍特。所以說(shuō)上面介紹的兩點(diǎn)短路操作情況,其實(shí)說(shuō)得都是一種情況:數(shù)據(jù)源無(wú)限魔吐,結(jié)果有限扎筒,這樣才能在有限時(shí)間內(nèi)得到結(jié)果。
? ? ? ?另外需要明確的是:Stream操作的過(guò)程中酬姆,是不會(huì)對(duì)源數(shù)據(jù)做任何修改的嗜桌,在經(jīng)歷過(guò)Stream處理后的結(jié)果,一般都是存儲(chǔ)到另外的一個(gè)空間中辞色,對(duì)源數(shù)據(jù)沒(méi)有任何影響骨宠。
Intermediate常用的方法:map (mapToInt, flatMap 等)、 filter相满、 distinct层亿、 sorted、 peek立美、 limit匿又、 skip、 parallel建蹄、 sequential碌更、 unordered
Terminal操作常用的方法:forEach、 forEachOrdered洞慎、 toArray痛单、 reduce、 collect劲腿、 min桦他、 max、 count谆棱、 anyMatch快压、 allMatch、 noneMatch垃瞧、 findFirst蔫劣、 findAny、 iterator
Short-circuiting常用操作:anyMatch个从、 allMatch脉幢、 noneMatch歪沃、 findFirst、 findAny嫌松、 limit
? ? ? ?上面這些方法只是作為一個(gè)簡(jiǎn)單記錄沪曙,后面介紹使用的時(shí)候,會(huì)具體用到萎羔,到時(shí)就可以了解它們的功能了液走,這里只是記錄下來(lái),作為一個(gè)了解贾陷。
Stream的使用
? ? ? ?前面扯了這么多缘眶,實(shí)際上仍然沒(méi)有具體說(shuō)怎么使用,有了前面的概念介紹髓废,下面介紹使用的時(shí)候巷懈,就會(huì)清楚多了,不會(huì)像第一次看到那樣毫無(wú)頭緒了慌洪。流的使用其實(shí)就是實(shí)現(xiàn)一個(gè) filter-map-reduce 過(guò)程顶燕,過(guò)程中使用Lambda表達(dá)式,是一種函數(shù)式編程冈爹,對(duì)于函數(shù)式編程概念不清楚的涌攻,可以了解一下。其實(shí)我們?nèi)绻煜s就會(huì)很快上手使用了犯助,這個(gè)跟ES6中遍歷數(shù)組的操作類似。
Stream對(duì)象的構(gòu)建
Stream的構(gòu)建其實(shí)就是前面介紹的source的生成方式中介紹的那樣即可:
//使用Stream.of構(gòu)建
Stream stream = Stream.of("a", "b", "c");
//使用數(shù)組構(gòu)建
String[] array = {"a", "b", "c"};
stream = Arrays.stream(array);
//使用Collection構(gòu)建
List<String> list = Arrays.asList(array);
stream = list.stream();
? ? ? ?Stream是支持泛型的维咸,但是對(duì)于基本數(shù)據(jù)類型和對(duì)應(yīng)的包裝類型剂买,存在自動(dòng)拆裝箱的情況,這個(gè)過(guò)程比較耗費(fèi)性能癌蓖,所以這里Stream提供了IntStream瞬哼、LongStream、DoubleStream這三種特殊的Stream用以基本數(shù)據(jù)類型的計(jì)算租副。目前只有這三種(java8版本)坐慰。
//數(shù)值流的構(gòu)造
IntStream.of(1, 2, 3);
//range和rangeClosed構(gòu)建
//range表示開(kāi)區(qū)間 [1, 3),rangeClosed表示閉區(qū)間 [1, 3]
IntStream.range(1, 3);// 1, 2
IntStream.rangeClosed(1, 3);// 1, 2, 3
Stream具體操作
這里簡(jiǎn)單介紹一些關(guān)于Stream在具體代碼中的使用方式,如果想要了解更多用僧,可以考慮查閱更多的文檔结胀。
//一個(gè)簡(jiǎn)單的場(chǎng)景:字符串集合中,將所有字符串全部轉(zhuǎn)換成大寫(xiě)
String[] words = {'a', 'b', 'c', 'd'};
Stream stream = Arrays.toList(words).stream();
List<String> output = stream.map(String::toUpperCase)
.collect(Collectors.toList())责循;
? ? ? ?這里使用了“String::toUpperCase”這種寫(xiě)法糟港,這個(gè)也是Java8新引入的特性Supplier,這里就不再深入介紹它了院仿,這里只要知道秸抚,它的功能就是找到String類中定義的toUpperCase方法速和,然后將stream中的每個(gè)元素作為toUpperCase方法的入?yún)ⅲ煌U{(diào)用它并返回新的結(jié)果剥汤。
? ? ? ?上面介紹中颠放,可以看到collect其實(shí)就是一個(gè)Terminal操作,中間的這個(gè)map就是一個(gè)Intermediate操作吭敢,當(dāng)然這個(gè)Intermediate操作還可以繼續(xù)添加碰凶,對(duì)源數(shù)據(jù)繼續(xù)進(jìn)行轉(zhuǎn)換。這里再回頭看前面介紹過(guò)的Intermediate操作常用的方法省有,可以看到痒留,這個(gè)過(guò)程有很多方法可以調(diào)用,比如:filter過(guò)濾用的蠢沿、forEach遍歷用的等等伸头。
? ? ? ?Terminal操作永遠(yuǎn)都是在鏈條的最后,并且只能調(diào)用一次舷蟀,一旦執(zhí)行后恤磷,Stream上的元素就被“消費(fèi)”掉了,無(wú)法對(duì)一個(gè)Stream進(jìn)行兩次Terminal操作野宜。例如:
stream.forEach(element -> doOneThing(element));
stream.forEach(element -> doAnotherThing(element));
? ? ? ?這里的forEach就是一個(gè)Terminal操作扫步,如果確實(shí)有需要對(duì)其中的每一個(gè)數(shù)據(jù)有其他操作,可以添加到Intermediate操作過(guò)程中匈子,這里以peek方法為例:
Stream.of("one","two","three","four","five")
.filter(e -> e.length() > 3)
.peek(e -> System.out.println("Filtered value: " + e))
.map(String::toUpperCase)
.peek(e -> System.out.println("Mapped value: " + e))
.collect(Collectors.toList());
? ? ? ?上面的代碼中河胎,Intermediate操作就疊加了很多層,然后collect操作結(jié)束整個(gè)流過(guò)程虎敦,這里也很明顯能感受到Stream編程帶來(lái)的好處:代碼的可讀性大幅度提高了游岳,而且代碼比較優(yōu)雅。我們不論熟不熟悉上面的具體語(yǔ)法其徙,但是通過(guò)閱讀上面的代碼胚迫,我們很明看可以知道它到底是在干什么:對(duì)字符串集合過(guò)濾出長(zhǎng)度大于3的元素,輸出通過(guò)校驗(yàn)的元素唾那,將通過(guò)的元素轉(zhuǎn)成大寫(xiě)访锻,再次輸出結(jié)果,最后返回List<String>闹获,這個(gè)就是可讀性期犬,如果按照傳統(tǒng)的方式,我們需要一遍又一遍迭代遍歷集合避诽,才能達(dá)到上面的效果哭懈,代碼量會(huì)明顯加大,而且代碼可讀性非常差茎用,不夠一目了然遣总。
? ? ? ?仔細(xì)看前面說(shuō)過(guò)的Terminal常用操作方法和Short-circuiting常用操作睬罗,可以發(fā)現(xiàn)里面是有些重疊的,比如:findFirst旭斥、findAll容达、anyMatch等等。這類操作根據(jù)方法名稱就可以了解它們的功能了垂券,這里我就不再贅述了花盐,之所以提一下,主要是需要注意一下:Short-circuiting與其他兩類操作的界限是不明顯的菇爪,這個(gè)仔細(xì)一想也能明白算芯,它們分類的出發(fā)點(diǎn)都不同,所以有重合很正常凳宙。
自定義生成流
? ? ? ?這里我們需要用到Stream.generate或者Stream.iterate方法熙揍,通過(guò)實(shí)現(xiàn) Supplier 接口,你可以自己來(lái)控制流的生成氏涩。把 Supplier 實(shí)例傳遞給 Stream.generate() 生成的 Stream届囚。它默認(rèn)是串行而且無(wú)序的。現(xiàn)在以生成10個(gè)隨機(jī)數(shù)為例:
//傳統(tǒng)方式:借助于Random
Random seed = new Random();
Supplier<Integer> random = seed::nextInt;
Stream.generate(random).limit(10).forEach(System.out::println);
?
//Stream.generate方式
IntStream.generate(() -> (int) (System.nanoTime() % 100))
.limit(10)
.forEach(System.out::println);
? ? ? ?上面代碼中是尖,后者采用了System.nanoTime()產(chǎn)生系統(tǒng)隨機(jī)數(shù)意系,因?yàn)樗菬o(wú)限的,如果不進(jìn)行短路操作饺汹,Stream中會(huì)不斷產(chǎn)生隨機(jī)數(shù)蛔添,沒(méi)有邊界,所以必須要limit一下兜辞,獲取前十個(gè)即可迎瞧。generate方法里面的參數(shù)我們也可以自己手動(dòng)實(shí)現(xiàn),只要寫(xiě)一個(gè)類實(shí)現(xiàn)Supplier即可弦疮,需要什么邏輯在具體實(shí)現(xiàn)類里面寫(xiě)清楚就行夹攒。
class MusicSupplier implements Supplier<Music> {
private int index = 0;
@Override
public Music get() {
return new Music(index++, "Music_" + index);
}
}
class Music{
private int id;
private String name;
public Music(int id, String name) {
this.id = id;
this.name = name;
}
...省略getter和setter
}
class Test {
public static void main(String[] args) {
//生成10個(gè)Music對(duì)象并打印結(jié)果
Stream.generate(new MusicSupplier())
.limit(10)
.forEach(m -> System.out.println(m.getId() + "---" + m.getName()));
}
}
? ? ? ?下面再來(lái)說(shuō)一下Stream.iterate蜘醋,它其實(shí)跟reduce操作很像胁塞,接受一個(gè)種子值和一個(gè)UnaryOperator(一元操作符,例如函數(shù) f )压语,然后種子值成為 Stream 的第一個(gè)元素啸罢,f(seed) 為第二個(gè),f(f(seed)) 第三個(gè)胎食,以此類推扰才。中學(xué)階段學(xué)到的數(shù)列其實(shí)就可以用這種方式,如:等差數(shù)列厕怜、等比數(shù)列之類的衩匣。
//等差為3的數(shù)列
Stream.iterate(0, n -> n + 3).limit(10). forEach(x -> System.out.print(x + " "));
Collectors 的 reduction 操作
? ? ? ?這里的Collectors是java.util.stream包下的一個(gè)輔助類蕾总,主要是輔助流的輸出結(jié)果的轉(zhuǎn)換,主要說(shuō)的是groupingBy和partitionBy琅捏,reduction翻譯過(guò)來(lái)就是減少生百、還原的意思,其實(shí)就是類似于數(shù)據(jù)庫(kù)中的分組一樣柄延,比如現(xiàn)在有一個(gè)person表蚀浆,我們需要根據(jù)性別分組統(tǒng)計(jì),統(tǒng)計(jì)男女各對(duì)應(yīng)有多少人搜吧。整體來(lái)說(shuō)市俊,它最終的結(jié)果是整合后的結(jié)果。下面有個(gè)示例:隨機(jī)生成100個(gè)Person對(duì)象滤奈,
private static class PersonSupplier implements Supplier<Person> {
private int index = 0;
private Random random = new Random();
@Override
public Person get() {
return new Person(index++, "StormTestUser" + index, random.nextInt(100));
}
}
?
private static class Person {
public int no;
private String name;
private int age;
public Person (int no, String name, int age) {
this.no = no;
this.name = name;
this.age = age;
}
public String getName() {
System.out.println(name);
return name;
}
public int getAge() {
return age;
}
}
?
public class Test{
public static void main(String[] args) {
//這里有一個(gè)例子:根據(jù)年齡分組
Map<Integer, List<Person>> personGroups =
Stream.generate(new PersonSupplier())
.limit(100)
.collect(Collectors.groupingBy(Person::getAge));
Iterator it = personGroups.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<Integer, List<Person>> persons = (Map.Entry) it.next();
System.out.println("Age " + persons.getKey() + " = " + persons.getValue().size());
}
//按照未成年人和成年人歸組
Map<Boolean, List<Person>> children =
Stream.generate(new PersonSupplier())
.limit(100)
.collect(Collectors.partitioningBy(p -> p.getAge() < 18));
System.out.println("Children number: " + children.get(true).size());
System.out.println("Adult number: " + children.get(false).size());
}
}
Stream的優(yōu)勢(shì)
提高代碼可讀性:這個(gè)通過(guò)前面的介紹也應(yīng)該可以有一個(gè)清晰的感受摆昧,代碼的條理性和可讀性都有很大的提升,可讀性高僵刮,維護(hù)起來(lái)就比較方便据忘,軟件主要的生命周期都是在維護(hù)階段,所以代碼的可維護(hù)性以及維護(hù)成本非常重要搞糕。
降低代碼量勇吊,提高靈活度:因?yàn)閭鹘y(tǒng)的集合操作,必定擺脫不了遍歷操作窍仰,而Stream隱式遍歷就大大減少了編碼人員的工作量汉规,我們不需要關(guān)注具體的遍歷情況,只需要將便利過(guò)程中需要加入的邏輯放到 Intermediate 操作中驹吮。而且因?yàn)?Intermediate 操作的可重復(fù)性针史,后期如果需要添加額外的處理邏輯,直接在代碼鏈上添加或刪除就行碟狞,方便靈活啄枕。
無(wú)限數(shù)據(jù)量:理論上,source的數(shù)據(jù)量可以是無(wú)限的族沃,只要有相應(yīng)的短路操作或者能夠快速得到結(jié)果的操作即可频祝,這些在對(duì)于一些海量數(shù)據(jù)的情況下,Stream提供了一種更快捷優(yōu)雅的解決方式脆淹。
支持并行:可以充分利用現(xiàn)代計(jì)算機(jī)多核的優(yōu)勢(shì)常空,極大地提高了數(shù)據(jù)的處理速度。
Stream的使用場(chǎng)景
多個(gè)集合操作
? ? ? ?比如:先進(jìn)行filter過(guò)濾盖溺,然后再forEach漓糙,這時(shí)傳統(tǒng)做法就是先遍歷一次進(jìn)行顧慮,然后再一次遍歷過(guò)濾后的數(shù)據(jù)集合烘嘱,此時(shí)如果使用Stream操作就變得優(yōu)雅簡(jiǎn)單昆禽,并且非常高效蝗蛙。
對(duì)性能要求比較高
? ? ? ?這里就需要提升數(shù)據(jù)的處理速度,比如并行模式醉鳖,這時(shí)傳統(tǒng)的方案需要額外添加許多邏輯歼郭,甚至是并發(fā)的邏輯,但是采用Stream的方式非常簡(jiǎn)單快捷辐棒,如果遇到這種場(chǎng)合病曾,可以考慮Stream的方案。
函數(shù)式編程
? ? ? ?Stream的設(shè)計(jì)初衷之一就是為了在Java中引入函數(shù)式的編程風(fēng)格漾根,如果團(tuán)隊(duì)中確實(shí)有這種偏好或者規(guī)定泰涂,可以考慮使用Stream。
無(wú)界限
? ? ? ?集合的大小是有界的辐怕,但是流不需要逼蒙,有許多短路操作可以允許我們?cè)谟邢薜臅r(shí)間內(nèi)完成無(wú)限流的計(jì)算,如果遇到這種情況寄疏,只能采用Stream方式是牢,因?yàn)閭鹘y(tǒng)方式不能達(dá)到這種效果。
其他問(wèn)題
paralleStream的線程安全問(wèn)題
? ? ? ?Stream支持并行模式陕截,但是如果使用不當(dāng)驳棱,很容易陷入誤區(qū)。這里舉一個(gè)簡(jiǎn)單的例子:分別用串行农曲、并行以及加鎖的方式往三個(gè)list集合中添加一萬(wàn)個(gè)元素:
private static List<Integer> list1 = new ArrayList<>();
private static List<Integer> list2 = new ArrayList<>();
private static List<Integer> list3 = new ArrayList<>();
private static Lock lock = new ReentrantLock();
?
public static void main(String[] args) {
IntStream.range(0, 10000).forEach(list1::add);
?
IntStream.range(0, 10000).parallel().forEach(list2::add);
?
IntStream.range(0, 10000).forEach(i -> {
lock.lock();
try {
list3.add(i);
}finally {
lock.unlock();
}
});
?
System.out.println("串行執(zhí)行的大猩缃痢:" + list1.size());
System.out.println("并行執(zhí)行的大小:" + list2.size());
System.out.println("加鎖并行執(zhí)行的大腥楣妗:" + list3.size());
}
? ? ? ?串行和加鎖的方式每次得到的結(jié)果都是10000形葬,是正確的,但是中間的并行執(zhí)行每次結(jié)果都不一樣暮的,很明顯笙以,并行模式下,并不能保證線程安全冻辩。針對(duì)這種情況猖腕,它的解決方案是使用collect和reduce接口,深層的原因涉及到Stream的具體原理微猖,這里就不再深入谈息,只是記住一個(gè)結(jié)論:paralleStream里直接去修改變量是非線程安全的缘屹,但是采用collect和reduce操作就是滿足線程安全的了凛剥。