java8——使用流

概要

流讓你從外部迭代轉向內部迭代。這樣蜀涨,你就用不著寫下面這樣的代碼來顯式地管理數(shù)據(jù)集合的迭代(外部迭代)了:

List<Dish> vegetarianDishes = new Arraylist<>();
for(Dish d: menu){
    if(d.isVegetarian()){
        vegetarianDishes.add(d);
    }
}

現(xiàn)在可以使用支持filter和collect操作的Stream API(內部迭代)管理對集合數(shù)據(jù)的迭代瞎嬉。你只需要將篩選行為操作參數(shù)傳遞給fileter方法就行了:

List<Dish> vegetarianDishes = menu.stream()
.filter((d)->{d.isVegetarian}) //這里的類型可以省略
.collect(toList());

這種處理數(shù)據(jù)的方式很有用,因為你讓Stream API管理如何處理數(shù)據(jù)厚柳。這樣Stream API就可以在背后進行多種優(yōu)化佑颇。此外,使用內部迭代的話草娜,Stream API可以決定并行運行你的代碼挑胸。這要是用外部迭代的話就辦不到了,因為你只能用單一的線程挨迭代宰闰。

篩選和切片

用謂詞篩選

Streams接口支持filter方法茬贵。該操作會接受一個謂詞(一個返回boolean的函數(shù))作為參數(shù),并返回一個包括所有符合謂詞的元素的流移袍。列如:

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

篩選各異的元素

流還支持一個叫做distinct的方法解藻,它會返回一個元素各異(根據(jù)流所生成元素的hashCode和equals方法實現(xiàn))的流。列如葡盗,以下代碼會篩選出列表中所有的偶數(shù)螟左,并確保沒有重復。

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

截斷流

流支持limit(n)方法觅够,該方法會返回一個不超過給定長度的流胶背。所需的長度座位參數(shù)傳遞給limit。如果留是有序的喘先,則最多會返回前n個元素钳吟。比如,你可以建立一個List窘拯,選出熱量超過300卡路里的頭三道菜:

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

可以看到红且,該方法只選出了符合謂詞的頭三個元素,然后就立即返回了結果涤姊。
請注意limit也可以用再無序流上暇番,比如源是一個Set。這種情況下思喊,limit的結果不會以任何順序排列壁酬。

跳過元素

流和支持skip(n)方法,返回一個扔掉了前n個元素的流。如果流中元素不足n個厨喂,則返回一個空流和措。下面的代碼將跳過超過300卡路里的頭兩道菜,并返回剩下的蜕煌。

List<Dish> dishes = menu.stream()
.filter(d -> d.getCalories > 300)
.skip(2)
.coolect(toList());
image

映射

對流中的每一個元素應用函數(shù)

流支持map方法派阱,它會接受一個函數(shù)作為參數(shù)。這個函數(shù)會被應用到每個元素上斜纪,并將其映射成一個新的元素(使用映射一詞贫母,是因為它和轉換類似,但其中的差別在于它是“創(chuàng)建一個新版本”而不是“修改”)盒刚。列如腺劣,下面代碼提取流中菜肴的名稱:

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

流的扁平化

已經看到如何使用map方法返回列表中每個單詞的長度了窍霞。讓我們拓展一下:對于一張單詞表炫彩,如何返回一張列表蛙卤,列出里面各不相同的字符呢凿叠?列如,給定單詞表["hello","world"], 你想要返回列表["h","e","l","o","w"拦惋,"r","d"].

可能會認為很容易媚送,可以把每個單詞映射成一張字符表噩咪,然后調用distinct來過濾重復的字符吩愧。

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

這個方法問題在于芋酌,傳遞給map方法的Lambda為每個單詞返回了一個String。因此雁佳,map返回的流實際上是Stream<String[]>類型的脐帝。你真正想要的是用Stream<String>來表示一個字符流。

image

可以使用FlatMap來解決這個問題:

List<String> uniqueCharacters = 
word.stream()
.map(w -> w.split(""))//將每個單詞轉為由其字母構成的數(shù)組
.flatMap(Array::stream)//將各個生成流扁平化為單個流
.distinct()
.collect(toList());

使用flatMap方法的效果是糖权,各個數(shù)組并不是分別映射成一個流堵腹,而是映射成流的內容。所有使用map(Arrays::stream)時生成的單個流都被合并起來温兼,即扁平化為一個流秸滴。下圖說明了flatMap的效果


image

flatMap方法讓你把一個流中的每個值都換成一個流,然后把所有的流連接起來成為一個流募判。

查找和匹配

另一個常見的數(shù)據(jù)處理套路是看看數(shù)據(jù)集中的某些元素是否匹配一個給定的屬性。Stream API通過allMatch,anyMatch,noneMatch,findFirst和findAny方法提供了這樣的工具咒唆。

檢查謂詞是否至少匹配一個元素 anyMatch

anyMatch方法可以回答“流中是否有一個元素能匹配給定的謂詞”届垫。比如,你可以用它來看看菜單里面是否有素食可以選擇

if(menu.stream().anyMatch(Dish::isVegetarian)){
    System.out.println("there is any vegetarian foot");
}

anyMatch方法返回一個boolean全释,因此是一個終端操作

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

allMatch方法的工作原理和anyMatch類似装处,但它會看看流中的元素是否都能匹配給定的謂詞。比如,你可以用它來看看菜品是否都有利于健康(卡路里小于1000卡路里):

boolean isHealthy = menu.stream().allMatch(s -> s.getCalories() < 1000);

noneMatch

和allMatch相對的是noneMatch妄迁。它可以確保流中沒有任何元素與給定的謂詞匹配寝蹈。比如你可以用noneMatch重寫前面的列子:

boolean isHealthy = menu.stream().noneMatch(s -> s.getCalories() >= 1000);

anyMatch,allMatch和noneMatch這三個操作都用到了我們所謂的短路,這就是大家熟悉的java中的&&和||運算符短路在流中的版本登淘。

查找元素 findAny

findAny方法將返回當前流中的任意元素箫老。它可以與其他流操作結合使用。比如黔州,你想找到一道素食菜肴耍鬓。你可以結合使用filter和findAny方法來實現(xiàn)這個查詢:

Optionnal<Dish> dish = menu.stream().filter(Dish:isVegetarian).findAny();

流水線將在后臺進行優(yōu)化使其只需走一遍,并在李勇短路找到結果時立即結束流妻。

Optional簡介

Optionnal<T>類(java.util.Optional)是一個容器類牲蜀,代表一個值存在或不存在。在上面的代碼中绅这。findAny可能什么元素都沒找到涣达。java8的庫設計人員引入了Optional<T>,這樣就不用返回中所周知的null了。

Optional里面幾種可以迫使你顯示的檢查值是否存在或處理值不存在的情形的方法也不錯证薇。

  1. isPresent() 將在Optional包含值的時候返回true峭判,否則返回false.
  2. ifPresent(Consumer<T> block)會在值存在的時候執(zhí)行給定的代碼塊。介紹了Consumer函數(shù)式接口棕叫;它讓你傳遞一個接收T類型的參數(shù)林螃,并返回void的lambda表達式
  3. T get()會在值存在的時候返回值,否則拋出一個NoSuchElement異常
  4. T orElse(T other) 會在值存在時返回值俺泣,否則返回一個默認值疗认。

查找第一個元素

找到第一個平法能被3整除的數(shù):

List<Integer> someNumbers = Arrays.asList(1,2,3,4,5);
Optional<Integer> firstSquareDivisibleByThree = someNumbers.stream().
map(x - x * x)
.filter(x -> x % 3 == 0)
.findFirst();

何時使用findFirst和findAny

你可能會想,為什么同時有findFirst和findAny呢伏钠?答案是==并行==横漏。找到第一個元素在并行上限制更多。如果你不關心返回的元素是哪個熟掂,請使用findAny缎浇,因為它在使用并行流時限制更少。

歸約

到目前為止赴肚,你見到過的終端操作都是返回一個boolean(allMatch之類的)素跺,void(forEach)或Optionnal對象(findAny)。也見過了使用collect來將流中的所有元素組合成一個List誉券。

本節(jié)中指厌,你將看到如何把一個流中的流中的元素組合起來,使用reduce操作來表達更復雜的查詢踊跟。

元素求和

在研究reduce方法之前踩验,先來看看如何使用for-each循環(huán)來對數(shù)字列表中的元素求和:

int sum = 0;
for (int x:numbers){
    sum += x;
}

要是還能把所有的數(shù)字相乘,而不必去復制粘貼這段代碼,豈不是更好箕憾?這正是reduce操作的用武之地牡借,它對這種重復應用的模式做了抽象∠欤可以以下面這樣對流中所有的元素求和:

int sum = numbers.stream().reduce(0,(a,b)->a+b);

reduce接收兩個參數(shù):

  1. 一個初始值钠龙,這里是0;
  2. 一個BinaryOperater<T> 來將連個 元素結合起來產生一個新值扁远。
    如果把所有的元素相乘也很簡單
int sum = numbers.stream().reduce(1,(a,b)->a * b);
image

無初始值
reduce還有一個重載的變體俊鱼,它不接受初始值,但是會返回一個Optional對象:

Optional<Integer> sum = numbers.stream().reduce((a,b) -> (a + b));

為什么它返回一個Optional<Integer>呢畅买?考慮流中沒有任何元素的情況并闲。reduce操作無法返回其和,因為它沒有初始值谷羞。這就是為什么結果被包裹在一個Optional對象里帝火,以表明和可能不存在。

最大值和最小值

reduce操作會考慮新值和流中下一個元素湃缎,并產生一個新的最大值犀填,值到整個流消耗完∩のィ可以用下面的代碼來計算最大值

int maxNumber = numbers.stream().reduce(0,(a,b) -> Interger.max(a,b));
image

終端操作和中間操作

image

數(shù)值流

原始類型流特性

java8 引入了三個原始類型特化流接口來解決這個問題:IntStream,DoubleStream和LongStream,分別將流中的元素特化為int九巡,long和double,從而避免了暗含的裝箱成本。每個接口都帶來了進行常用數(shù)值規(guī)約的新方法蹂季,比如對數(shù)值流求和的sum冕广,找到最大元素的max。==這些特化的原因并不在于流的復雜性偿洁,而是裝箱造成的復雜性——即類似int和Integer之間的效率差==異撒汉。

映射到數(shù)值流

將流轉換為特化版本的常用方法是mapToInt,mapToDouble和mapToLong。這些方法和前面說的map方法的功能做方式一樣涕滋,只是他們返回的是一個特化流睬辐,而不是Stream<T>。列如宾肺,你可以像下面這樣用mapToInt對menu中的卡路里求和:

int totalCalories = menu.stream()
                    .mapToInt((s) -> s.getCalories)
                    .sum();

這里溯饵,mapToInt會從每道菜中提取熱量(用一個Integer表示),并返回一個IntStream(而不是一個Stream<Integer>)爱榕。然后你就可以調用Integer接口中定義的sum方法瓣喊,對卡路里進行求和。==請注意黔酥,如果流是空的,sum默認返回0==.IntStream還支持其他的方便方法,如max跪者,min棵帽,average等等。

轉換回對象流

同樣渣玲,一旦有了數(shù)值流逗概,你可能會想把它轉換回非特化流。列如忘衍,IntStream上的操作只能產生原始整數(shù):IntStream的map操作接收的Lambda必須接收int并且返回int(一個IntUnaryOperator)逾苫。但是你可能想要生成另一類值,比如Dish枚钓。為此铅搓,你需要訪問Stream接口中定義的那些更廣義的操作。要把原始流轉換成一般的流(每個int都會裝箱成一個Integer),可以使用boxed方法搀捷。

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

構建流

由值創(chuàng)建流

可以使用靜態(tài)方法Stream.of星掰,通過顯示值創(chuàng)建一個流。它可以接受任意數(shù)量的參數(shù)嫩舟。列如氢烘,一下代碼直接使用Stream.of創(chuàng)建了一個字符串流。然后你可以將字符串轉化為答謝家厌,再一個個的打印出來:

Stream<Stream> stream = Stream.of("java8","lambdas","in","action");
Stream.map((s)->s.toUpperCase).forEach((s)->System.out.println(s));

你可以使用empty得到一個空流播玖,如下所示:
Stream<String> emptyStream = Stream.empty();

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

可以使用靜態(tài)方法Arrays.stream從數(shù)組創(chuàng)建一個流。它接受一個數(shù)組作為參數(shù)饭于。例如蜀踏,可以講一個原始類型int的數(shù)組轉換成一個IntStream。

int[] numbers ={1,2,3,4,5,6,7}
int sum = Arrays.stream(numbers).sum();

由文件生成流

java中用于處理文件等I/O操作的NIO API(非阻塞I/O)已經更新镰绎,以便利用Sream API
java.nio.file.Files中的很多靜態(tài)方法都會返回一個流脓斩。例如,一個很有的方法是File.lines,它會返回一個由指定文件中的各行構成的字符串流畴栖∷婢玻可以yoga這個方法看看一個文件中有多少各不相同的詞:

long uniqueWords = 0;
try{
    Stream<String> lines = Files.lines(Paths.get("data.txt"),Charset.defaultCharset());
    uniqueWords = lines.flatMap(line ->Arrays.stream(line.split(""))).distinct().count();
}catch(IOExcepiton e){
    
}

由函數(shù)生成流:創(chuàng)建無限流

Stream API提供了兩個靜態(tài)方法來從函數(shù)生成流:Stream.iterate和Stream.generate。這兩個操作可以創(chuàng)建所謂的無限流:不像從固定集合創(chuàng)建的流的那樣有固定大小的流吗讶。由iterate和generate產生的流會用給定的函數(shù)按需創(chuàng)建值燎猛,因此可以無窮地計算下去!一般來說照皆,應該使用limit(n)來對這種流加以限制重绷,以避免打印無窮多個值。

迭代 iterate

先看一個簡單的iterate的列子膜毁,然后再解釋:

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

這個流將會無限的打印昭卓,2愤钾,4,候醒,6能颁,8,10.此操作將生成一個無限流——這個流沒有結尾倒淫,因為值是按需計算的伙菊,可以永遠計算下去。

斐波那契序列

Stream.iterate(new int[]{0,1},t->new int[]{t[1],t[0]+t[1])
.limit(10)
.map(t->t[0])
.forEach(System.out::println);

生成 generate

與iterate方法類似敌土,generate方法也可以讓你按需生成一個無限流镜硕。但是generate不是依次對每個新生成的值應用函數(shù)的。它接受一個Supplier<T>類型的Lambda提供新的值返干。

Stream.generate(Math::random) //不會對生成的值計算兴枯。
.limit(5)
.forEach(System.out::println);
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市犬金,隨后出現(xiàn)的幾起案子念恍,更是在濱河造成了極大的恐慌,老刑警劉巖晚顷,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件峰伙,死亡現(xiàn)場離奇詭異,居然都是意外死亡该默,警方通過查閱死者的電腦和手機瞳氓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來栓袖,“玉大人匣摘,你說我怎么就攤上這事」危” “怎么了音榜?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長捧弃。 經常有香客問我赠叼,道長,這世上最難降的妖魔是什么违霞? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任嘴办,我火速辦了婚禮,結果婚禮上买鸽,老公的妹妹穿的比我還像新娘涧郊。我一直安慰自己,他們只是感情好眼五,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布妆艘。 她就那樣靜靜地躺著彤灶,像睡著了一般。 火紅的嫁衣襯著肌膚如雪双仍。 梳的紋絲不亂的頭發(fā)上枢希,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天桌吃,我揣著相機與錄音朱沃,去河邊找鬼。 笑死茅诱,一個胖子當著我的面吹牛逗物,可吹牛的內容都是我干的。 我是一名探鬼主播瑟俭,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼翎卓,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了摆寄?” 一聲冷哼從身側響起失暴,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎微饥,沒想到半個月后逗扒,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡欠橘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年矩肩,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片肃续。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡黍檩,死狀恐怖,靈堂內的尸體忽然破棺而出始锚,到底是詐尸還是另有隱情刽酱,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布瞧捌,位于F島的核電站棵里,受9級特大地震影響,放射性物質發(fā)生泄漏察郁。R本人自食惡果不足惜衍慎,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望皮钠。 院中可真熱鬧稳捆,春花似錦、人聲如沸麦轰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至末荐,卻和暖如春侧纯,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背甲脏。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工眶熬, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人块请。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓娜氏,卻偏偏與公主長得像,于是被迫代替她去往敵國和親墩新。 傳聞我的和親對象是個殘疾皇子贸弥,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內容

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn)海渊,斷路器绵疲,智...
    卡卡羅2017閱讀 134,601評論 18 139
  • Java8 in action 沒有共享的可變數(shù)據(jù),將方法和函數(shù)即代碼傳遞給其他方法的能力就是我們平常所說的函數(shù)式...
    鐵牛很鐵閱讀 1,213評論 1 2
  • 第一章 為什么要關心Java 8 使用Stream庫來選擇最佳低級執(zhí)行機制可以避免使用Synchronized(同...
    謝隨安閱讀 1,482評論 0 4
  • 章節(jié)內容篩選臣疑、切片和匹配查找盔憨、匹配和規(guī)約使用數(shù)值范圍等數(shù)值流從多個源創(chuàng)建流無限流 篩選和切片 用謂詞篩選 Stre...
    謝隨安閱讀 3,852評論 0 0
  • 篩選和切片 映射 查找和匹配 規(guī)約 數(shù)值流 構建流 歡迎訪問本人博客:http://wangnan.tech 篩選...
    GhostStories閱讀 930評論 0 3