Java8-流-使用流

篩選和切片 映射 查找和匹配 規(guī)約 數(shù)值流 構(gòu)建流

歡迎訪問本人博客:http://wangnan.tech

篩選和切片

用謂詞篩選

filter方法
會接收一個(gè)謂詞(一個(gè)返回Boolean)作為參數(shù),并返回一個(gè)包括所有符號謂詞的元素的流

例子:篩選所有的素菜

List<Dish> vegetarianMenu = menu.stream().filter(Dish::isVegetarian).collect(toList());

篩選各異的元素

distinct方法
他會返回一個(gè)元素各異的流滩褥,實(shí)現(xiàn)原理是根據(jù)元素的hashCode和equals方法

例子:篩選偶數(shù)剪撬,且不重復(fù)

List<Integer> numbers = Arrays.asList(1,2,1,3,3,2,4);
number.stream().filter(i->1%2==0)
               .distinct()
               .forEach(System.out.println);

截?cái)嗔?/h2>

limit(n)方法
該方法會返回一個(gè)不超過給定長度的流法希,如果流是有序的歹袁,則最多會返回前n個(gè)元素

例子:選出熱量超過300卡路里的頭三道菜

List<Dish> dishes = menu.stream()
                        .filter(d-d.getCalories()>300)
                        .limit(3)
                        .collect(toList());

跳過元素

skip(n)方法坷衍,返回一個(gè)扔掉前n個(gè)元素的流,如果流中元素不足n個(gè)条舔,則返回一個(gè)空流枫耳,請注意limit(n)和skip(n)是互補(bǔ)的

映射

一個(gè)常見的數(shù)據(jù)處理套路就是從某些對象中選擇信息,比如在sql里面孟抗,可以從表中選擇一列

對流中每個(gè)元素應(yīng)用函數(shù)

map方法
它會接收一個(gè)函數(shù)作為參數(shù)迁杨,這個(gè)函數(shù)會被應(yīng)用到每個(gè)元素上,并將其映射成一個(gè)新的元素(注意是創(chuàng)建一個(gè)新的版本凄硼,而不是去修改)

例子:提取菜肴的名稱

List<String> dishNames = menu.stream().map(Dish::getName).collect(toList());

流的扁平化

例子:對應(yīng)一張單詞表仑最,如果返回一個(gè)列表,列出里面各不相同的字符
比如單詞列表["Hello","Woeld"]你想要返回的列表["H","e","l","o","W","r","d"]

你可能會覺得很容易帆喇,調(diào)用distinct方法就可以了

words.stream()
    .map(word->word.split(""))
    .distinct()
    .collect(toList());

這個(gè)方法的問題在于警医,傳遞給map方法的lambda為每個(gè)單詞返回了一個(gè)String[],因此map返回的流實(shí)際上是Stream<String[]>類型,我們真正想要的是Stream<String>

幸好有flatMap來解決這個(gè)問題

嘗試1:使用map和Arrays.stream()
首先坯钦,你需要一個(gè)字符流预皇,而不是數(shù)組流,有一個(gè)Arrays.stream()的方法婉刀,可以接受一個(gè)數(shù)組并產(chǎn)生一個(gè)流

words.stream()
    .map(word->word.split(""))
    .map(Arrays::stream)
    .distinct()
    .collect(toList());

這個(gè)方案仍然搞不定吟温!因?yàn)楝F(xiàn)在得到的是一個(gè)流的列表,你先是把每個(gè)單詞轉(zhuǎn)換成一個(gè)字母數(shù)組,然后把每個(gè)數(shù)組變成一個(gè)獨(dú)立的流突颊。

嘗試2:使用flatMap

words.stream()
    .map(word->word.split(""))
    .flatMap(Arrays::stream)
    .distinct()
    .collect(toList());

flatMap方法的效果是鲁豪,各個(gè)數(shù)組并不是分別映射成一個(gè)流,而是映射成流的內(nèi)容律秃,所有使用map(Arrays::stream)時(shí)生成的單個(gè)流都被合并起來爬橡,即扁平化為一個(gè)流,

一言以蔽之棒动,flatMap方法讓你把一個(gè)流中的每個(gè)值都換成另一個(gè)流糙申,然后把所有的流連接起來成為一個(gè)流

查找和匹配

檢查謂詞是否至少匹配一個(gè)元素

anyMatch方法
可以回答“流中是否有一個(gè)元素能匹配給定的謂詞”
例子:菜單里面是否有素食可選擇

if(menu.stream().anyMatch(Dish::isVegetarian)){
    ...
}

檢查謂詞是否匹配所有元素

allMatch()用法同上
與allMatch()相對的是noneMatch()

anyMatch allMatch noneMatch 三個(gè)操作都用到了我們所謂的短路,就是大家熟悉的java中的&&和||運(yùn)算符短路在流中的版本

查找元素

findAny方法
將返回當(dāng)前流中的任意元素

findFirst
找到第一個(gè)元素

規(guī)約

reduce操作表達(dá)更復(fù)雜的查詢船惨,比如"計(jì)算菜單中的總卡路里"或“菜單中卡路里最高的菜是哪一個(gè)” 這需要將流中的元素反復(fù)結(jié)合起來柜裸,得到一個(gè)值,比如Integer,這樣的查詢被歸類為規(guī)約操作粱锐,用函數(shù)式編程術(shù)語來說疙挺,這稱為折疊(fold)

求和

int producr = numbers.stream().reduce(1,(a,b)->a*b);

reduce操作是如何作用于一個(gè)流的:
lambda反復(fù)結(jié)合每個(gè)元素,知道流被規(guī)約為一個(gè)值

可以使用最更簡潔的代碼:

int producr = numbers.stream().reduce(0,Integer::sum);

reduce還有一個(gè)重載的變體怜浅,它不接受初始值铐然,返回一個(gè)Optional對象:
Optional<Integer> sum = numbers.stream().reduce((a,b)->(a+b));

最大值和最小值

Optional<Integer> max = numbers.stream().reduce(Integer::max)

Optional<Integer> min = numbers.stream().reduce(Integer::min)

總結(jié)下目前說到的操作

數(shù)值流

之前我們看到了可以使用reduce方法計(jì)算流中元素的總和,例如:

int calories = menu.stream()
                    .map(Dish::getCalories)
                    .reduce(0,Integer::sum);

這段代碼的問題是,它有一個(gè)暗含的裝箱成本锦爵,每個(gè)Integer都必須拆箱成一個(gè)原始類型,再進(jìn)行求和奥裸,要是可以直接像下面這樣調(diào)用sum方法险掀,豈不是更好

int calories = menu.stream()
                    .map(Dish::getCalories)
                    .sum();

這是不可能的,問題在于map方法會生成一個(gè)Straem<T>湾宙,雖然流中的元素是Integer類型樟氢,但Streams接口沒有定義sum方法,不要擔(dān)心侠鳄,Stream API還提供了原始類型流特化

原始類型流特化

Java9引入了三個(gè)原始類型特化接口來解決這個(gè)問題:IntStream埠啃,DoubleStream和LongStream,分別將流中的元素特化為int伟恶,long碴开,double,從而避免了暗含的裝箱成本,每個(gè)接口都帶了進(jìn)行常用數(shù)值規(guī)約的新方法博秫,比如對數(shù)值流求和的sum潦牛,找到最大元素的max,此外有必要時(shí)再把他們轉(zhuǎn)換回對象流的方法

1.映射到數(shù)值流
例子:

int calories = menu.stream()
                    .mapToInt(Dish::getCalories)
                    .sum();

2.轉(zhuǎn)換回對象流
把原始流轉(zhuǎn)換成一般流挡育,可以使用boxed方法

IntStream intStream = menu.stream().mapToInt(Dish::getCalories);
Stream<Integer> stream = intStream.boxed();

3.默認(rèn)值optionalInt
求和有默認(rèn)值0巴碗,但是如果計(jì)算intStream中最大的元素,就得換個(gè)法子了即寒,因?yàn)?是錯(cuò)誤的結(jié)果橡淆,我們知道Optional類,這是一個(gè)可以表示值存在或不存在的容器母赵,Optional可以用Integer逸爵、String等參考類型來參數(shù)化,對于三種原始流特化凹嘲,也分別有一個(gè)optional原始類的特化版本:OptionalInt,OptionalDouble,OptionalLong
例如:

OptionalInt maxCalories = menu.stream().mapToInt(Dish::getCalories).max();

數(shù)值范圍

java8引入了兩個(gè)可以用于IntStream和LongStream的靜態(tài)方法痊银,幫助生成這種范圍:range和rangeClosed。第一個(gè)參數(shù)接受起始值施绎,第二個(gè)參數(shù)接受結(jié)束值溯革。
例子:

InStream evenNumbers = IntStream.rangClosed(1,100).filter(n->n%2==0)

構(gòu)建流

本節(jié)介紹如何從值序、數(shù)組谷醉、文件來創(chuàng)建流致稀,甚至由生成函數(shù)來創(chuàng)建無限流

由值創(chuàng)建流

可以使用靜態(tài)方法Stream.of,它可以接受任意數(shù)量的參數(shù)
例如:以下代碼創(chuàng)建一個(gè)字符串流俱尼,然后你可以將字符串轉(zhuǎn)換為大寫抖单,再一個(gè)個(gè)打印出來

Stream<String> stream = Stream.of("Java 8","Lambda","In","Action");
stream.map(String::toUpperCase).forEach(System.out::println);

由數(shù)組創(chuàng)建流

可以使用靜態(tài)方法Arrays.stream從數(shù)組創(chuàng)建一個(gè)流,例子:

int[] numbers = {2,3,5,7,11,13};
int sum = Arrays.stream(number).sum();

由文件生成流

Files.lines方法,它會返回一個(gè)由指定文件中的各行構(gòu)成的字符串流

創(chuàng)建無限流

Stream API提供兩個(gè)靜態(tài)方法來從函數(shù)生成流:Stream.iterate和Stream.generate
這兩個(gè)操作可以創(chuàng)建所謂的無限流:他們產(chǎn)生的流會用給定的函數(shù)按需創(chuàng)建值矛绘,因此可以無窮無盡地計(jì)算下去耍休,一般來說,應(yīng)該來說應(yīng)該使用limit(n)來對這種流加以限制货矮,以避免打印無窮多個(gè)值

例子

Stream.iterate(0,n->n+2)
      .limit(10)
      .forEach(System.out.println)

generate不是依次對每個(gè)生成的值應(yīng)用函數(shù)的羊精,它接受一個(gè)Supplier<T>類型的lambda提供新的值

例子

Stream.generate(Math::random)
      .limit(5)
      .forEach(System.out::println)

小結(jié)

  • 流可以簡潔地表達(dá)復(fù)雜的數(shù)據(jù)處理查詢,流可以透明的并行化
  • 你可以使用filter囚玫、distinct喧锦、skip和limit對流做篩選和切片
  • 你可以使用map和flatMap提取或裝換流中的元素
  • 你可以使用findFirst和findAny方法查找流中的元素,你可以使用allMatch抓督、noneMatch和anyMatch方法讓流匹配給定的謂詞
  • 這些方法都利用了短路:找到結(jié)果就立即停止計(jì)算燃少,沒有必要處理整個(gè)流
  • 你可以利用reduce方法將流中的所有元素迭代合并成一個(gè)結(jié)果,例如求和或查找最大元素
  • filter和map等操作是無狀態(tài)的铃在,他們并不儲存任何狀態(tài)阵具,reduce等操作要儲存狀態(tài)才能計(jì)算出一個(gè)值,sorted和distinct等操作也要儲存狀態(tài)定铜,因?yàn)樗麄冃枰蚜髦械乃性鼐彺嫫饋聿拍芊祷匾粋€(gè)新的流怔昨,這種操作稱為有狀態(tài)操作
  • 流不僅可以從集合創(chuàng)建,也可以從宿稀、數(shù)組趁舀、文件以及iterate與generate等特定方法創(chuàng)建
  • 無限流是沒有固定大小的流

(注:內(nèi)容整理自《Java8實(shí)戰(zhàn)》)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市祝沸,隨后出現(xiàn)的幾起案子矮烹,更是在濱河造成了極大的恐慌,老刑警劉巖罩锐,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件奉狈,死亡現(xiàn)場離奇詭異,居然都是意外死亡涩惑,警方通過查閱死者的電腦和手機(jī)仁期,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來竭恬,“玉大人跛蛋,你說我怎么就攤上這事∪叮” “怎么了赊级?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長岔绸。 經(jīng)常有香客問我理逊,道長橡伞,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任晋被,我火速辦了婚禮兑徘,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘羡洛。我一直安慰自己挂脑,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布翘县。 她就那樣靜靜地躺著,像睡著了一般谴分。 火紅的嫁衣襯著肌膚如雪锈麸。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天牺蹄,我揣著相機(jī)與錄音忘伞,去河邊找鬼。 笑死沙兰,一個(gè)胖子當(dāng)著我的面吹牛氓奈,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播鼎天,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼舀奶,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了斋射?” 一聲冷哼從身側(cè)響起育勺,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎罗岖,沒想到半個(gè)月后涧至,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡桑包,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年南蓬,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片哑了。...
    茶點(diǎn)故事閱讀 38,137評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡赘方,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出弱左,到底是詐尸還是另有隱情蒜焊,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布科贬,位于F島的核電站泳梆,受9級特大地震影響鳖悠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜优妙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一乘综、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧套硼,春花似錦卡辰、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至雾鬼,卻和暖如春萌朱,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背策菜。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工疑枯, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留儡嘶,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像掌动,于是被迫代替她去往敵國和親遣疯。 傳聞我的和親對象是個(gè)殘疾皇子偎窘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容