摘要:將在非空的時(shí)候返回值,否則會(huì)拋出沒有這個(gè)元素的異常。構(gòu)建流現(xiàn)在我們已經(jīng)能夠使用從集合生成流了。由文件生成流不重復(fù)的單詞數(shù)預(yù)處理獲取流,使用后不用手動(dòng)關(guān)閉流。我們使用得到流,其中每個(gè)元素就是文本里的一行。
篩選和切片 filter
filter 會(huì)接受一個(gè)謂詞作為參數(shù),并返回符合該條件的元素流。
ListvegetarianMenu = menu .stream() .filter(Dish::getVegetarian) .collect(Collectors.toList());
會(huì)篩選出素食的菜肴。
distinctdistinct 會(huì)返回一個(gè)元素各異的流,也就是去重。
ListcaloriesMenu = menu .stream() .filter((Dish dish) -> 350 == dish.getCalories()) .map(Dish::getCalories) .distinct() .collect(Collectors.toList());
會(huì)篩選出卡路里為350的菜肴并去重(米飯和對(duì)蝦二選一)。
limit(n)limit(n) 會(huì)返回一個(gè)不超過給定長(zhǎng)度的流。
ListcaloriesMenu = menu .stream() .filter((Dish dish) -> 350 < dish.getCalories()) .limit(3) .collect(Collectors.toList());
List 是有序集合所以會(huì)順序篩選出三個(gè),而如果是 Set 無(wú)序的則不會(huì)以任何順序排列。
skip(n)skip(n) 返回一個(gè)人掉了前 n 個(gè)元素的流,和 limit(n) 是互補(bǔ)的。
List映射caloriesMenu = menu .stream() .filter((Dish dish) -> 350 < dish.getCalories()) .skip(3) .collect(Collectors.toList());
map 返回一個(gè)映射好的元素流。如果我們要找出每個(gè)菜肴的名稱有多長(zhǎng),那么可以這樣思考:第一,我們需要知道菜肴的名稱。第二,計(jì)算菜肴名稱的長(zhǎng)度。
List流的扁平化caloriesMenu = menu .stream() // 映射名稱字符串 .map(Dish::getName) // 映射字符串長(zhǎng)度 .map(String::length) .collect(Collectors.toList());
返回菜肴名稱的長(zhǎng)度已經(jīng)做到了,如果有奇怪的需求,比如將菜肴名稱打碎成字符并去重……
ListcaloriesMenu = menu .stream() // 映射名稱字符串 .map(Dish::getName) // 將字符串分割為字符數(shù)組 String[] .map((String string) -> string.split("")) // Arrays.stream() 可以接收一個(gè)數(shù)組并產(chǎn)生一個(gè)流,再調(diào)用 flatMap 將其轉(zhuǎn)成單個(gè)流 .flatMap(Arrays::stream) // 去重 .distinct() // 保存 .collect(Collectors.toList());
扁平化在這里就是讓字符數(shù)組不是分別映射成一個(gè)流,而是映射成流的內(nèi)容,然后將使用 Arrays::stream 時(shí)生成的單個(gè)流都被合并起來(lái)。
查找和匹配 anyMatchanyMatch 可以檢查謂詞是否至少匹配一個(gè)元素。
// 檢查是否至少含有一道是素食的菜肴 Boolean b = menu .stream() .anyMatch(Dish::getVegetarian);allMatch
allMatch 可以檢查謂詞是否全部匹配。
// 檢查是否全部是素食 Boolean b = menu .stream() .anyMatch(Dish::getVegetarian);noneMatch
noneMatch 可以檢查謂詞是否全部不匹配。
// 檢查是否全部不是素食 Boolean b = menu .stream() .noneMatch(Dish::getVegetarian);
以上這三種操作都用到了所謂的短路,也和 Java 中!、&&和||運(yùn)算符意義一樣。
findAnyfindAny 將返回當(dāng)前流中的任意元素。
OptionaldishOptional = menu .stream() .findAny();
Optional
isPresent() 將在非空的時(shí)候返回 true。
ifPresent(Consumer super T> consumer) 將在非空的時(shí)候執(zhí)行 Consumer 的代碼,再第二章的時(shí)候我們知道 Consumer 可以接受一個(gè)對(duì)象并進(jìn)行操作。
T get() 將在非空的時(shí)候返回值,否則會(huì)拋出沒有這個(gè)元素的異常。
T orElse(T other) 將在非空的時(shí)候返回值,否則返回一個(gè)默認(rèn)值。
比如上面的代碼可以變成這樣
menu.stream().findAny().ifPresent(System.out::println);
就可以打印輸出任意菜肴的信息。
findFirst與 findAny 類似,只不過返回有序集合的第一個(gè)元素。
OptionaldishOptional = menu .stream() .findFirst();
那么 findAny 和 findFirst 有什么區(qū)別呢?在并行計(jì)算里面,找到第一個(gè)元素會(huì)需要很多思考,所以一般在并行處理時(shí)用 findAny。
歸納reduce 操作可以吧一個(gè)流中的元素組合起來(lái),比如菜單里的總卡路里。此類查詢需要將流中所有元素反復(fù)結(jié)合起來(lái),得到一個(gè)值。這樣的查詢操作可以被歸類為 歸約 操作,用函數(shù)式編程語(yǔ)言的術(shù)語(yǔ)來(lái)說(shuō),這稱為 折疊。
元素求和平常使用 for-each 來(lái)處理總卡路里我們一般會(huì)這樣做
Integer integer = 0; for (Dish dish : menu) { integer += dish.getCalories(); }
在流中可以使用 reduce 方法來(lái)處理
Integer integer = menu .stream() .map(Dish::getCalories) .reduce(0, (Integer a, Integer b) -> a + b);
好像有點(diǎn)復(fù)雜,沒關(guān)系,接下來(lái)可以詳細(xì)講解。
首先我們用 map 將 Dish 對(duì)象映射成其卡路里值的 Integer 類型。然后調(diào)用 reduce 將每個(gè)映射好的 Integer 組合(相加)。其中 reduce 方法第一個(gè)參數(shù)代表初始值,后面跟著一個(gè) BinaryOperator
可以看一下 BinaryOperator 的源碼
@FunctionalInterface public interface BinaryOperatorextends BiFunction { public static BinaryOperator minBy(Comparator super T> comparator) { Objects.requireNonNull(comparator); return (a, b) -> comparator.compare(a, b) <= 0 ? a : b; } public static BinaryOperator maxBy(Comparator super T> comparator) { Objects.requireNonNull(comparator); return (a, b) -> comparator.compare(a, b) >= 0 ? a : b; } }
實(shí)際上我們調(diào)用的是第三章有提到過的 BiFunction 里的 apply 方法
@FunctionalInterface public interface BiFunction{ R apply(T t, U u); }
BinaryOperator 它接受兩個(gè)同類型參數(shù)并返回一個(gè)同類型的對(duì)象。
所以我們可以這樣來(lái)寫
BinaryOperatorintegerBinaryOperator = (Integer a, Integer b) -> a + b;
代表計(jì)算 a + b。而 reduce 自帶循環(huán),所以會(huì)在背后進(jìn)行類似于 a += b 一樣的邏輯操作。reduce還有一個(gè)重載的版本,可以不接受初始值返回一個(gè) Optional 對(duì)象。
Optionalinteger = menu .stream() .map(Dish::getCalories) .reduce((Integer a, Integer b) -> a + b);
用 Optional 的意義與之前一樣,考慮到了集合為空的情況。
最大和最小reduce 同樣可以用來(lái)做大小比較,因?yàn)槠湫枰?BinaryOperator 自帶有比較方法。
最大
Optionalinteger = menu .stream() .map(Dish::getCalories) .reduce(Integer::max);
其中 reduce(Integer::max) 是
reduce((Integer integer1, Integer integer2) -> Integer.max(integer1, integer2))
的縮寫。計(jì)算最小值只需要將 max 換成 min 就行。歸納方法的對(duì)比傳統(tǒng)的 for-each 優(yōu)勢(shì)是可以把內(nèi)部迭代抽象化,這讓其內(nèi)部可以并行化而不用我們自己去實(shí)現(xiàn)并行處理。
但是這種并行是有限制的,不是將所有的 stream 換成 parallelStream 就行了。
像 map 或 filter 等操作會(huì)從輸入流中獲取每一個(gè)元素并在輸出流中得到至多一個(gè)結(jié)果。這些操作一般是無(wú)狀態(tài)的,做并行是可行的。
像 reduce、max 等操作需要內(nèi)部狀態(tài)來(lái)累積結(jié)果,因?yàn)榍蠛涂隙ㄊ切枰皵?shù)的和加上現(xiàn)在選擇的這個(gè)元素,但無(wú)論哪個(gè)都是有限的,所以稱為有界,是可以直接做并行。
相反,如同 distinct 操作,是需要接受一個(gè)流再生成一個(gè)流,并且去重操作是需要知道先前的歷史流是什么樣的,例如把所有質(zhì)數(shù)倒序,就需要最大的那個(gè)質(zhì)數(shù),但這是不存在的,所以稱為無(wú)界狀態(tài)。做并行需要好生思考一番。
關(guān)于去重,其實(shí)可以換一種思路,將有序集合(List)轉(zhuǎn)為無(wú)序集合(Set)就自動(dòng)去重了。
Set數(shù)值范圍caloriesMenu = menu .stream() .filter((Dish dish) -> 350 == dish.getCalories()) .map(Dish::getCalories) .collect(Collectors.toSet());
在和數(shù)字打交道時(shí),比較常用的就是在一個(gè)數(shù)值范圍內(nèi)生成數(shù)字。Java 8引入了兩個(gè)可以用于 IntStream 和 LongStream 的靜態(tài)方法,幫助生成范圍:range 和 rangeClosed。是不包含結(jié)束值的,比如傳入(0,100)就會(huì)運(yùn)算到99結(jié)束。
比如我們需要統(tǒng)計(jì)0~100里偶數(shù)的個(gè)數(shù)(包含100)
long l = IntStream .rangeClosed(0, 100) .filter((int n) -> n % 2 == 0) .count();
這樣就統(tǒng)計(jì)出了51個(gè)偶數(shù)個(gè)數(shù),如果不包含100就用 range,會(huì)統(tǒng)計(jì)50個(gè)。
構(gòu)建流現(xiàn)在我們已經(jīng)能夠使用 Stream 從集合生成流了。那么如何從值序列、數(shù)組和文件生成流,甚至函數(shù)來(lái)創(chuàng)建無(wú)限流?
創(chuàng)建一個(gè)空流Stream由值創(chuàng)建流stringStream = Stream.empty();
StreamstringStream = Stream.of("qwe", "asd", "zxc"); stringStream.map(String::toUpperCase).forEach(System.out::println);
這樣會(huì)把小寫字符串全部轉(zhuǎn)成大寫。
由數(shù)組創(chuàng)建流int[] ints = {1, 2, 3, 4, 5}; int sum = Arrays.stream(ints).sum();
這樣會(huì)把所有的數(shù)字相加。
由文件生成流/* /Users/cciradih/java: Java Platform, Standard Edition (Java SE) lets you develop and deploy Java applications on desktops and servers, as well as in today"s demanding embedded environments. Java offers the rich user interface, performance, versatility, portability, and security that today"s applications require. */ // 不重復(fù)的單詞數(shù) long uniqueWords = 0; // 預(yù)處理獲取流,使用后不用手動(dòng)關(guān)閉流。 try (StreamstringStream = Files.lines(Paths.get("/Users/cciradih/java"), Charset.defaultCharset())) { // 從流中統(tǒng)計(jì) uniqueWords = stringStream // 扁平化字符串?dāng)?shù)組 .flatMap((String line) -> Arrays.stream(line.split(" "))) // 去重 .distinct() // 統(tǒng)計(jì) .count(); } catch (IOException e) { e.printStackTrace(); }
這里是用到了新的 NIO,以便利用 Stream。我們使用 Files.lines 得到流,其中每個(gè)元素就是文本里的一行。然后使用 split 拆分成單詞,并用 flatMap 將其扁平化。最后用 distinct 去重再用 count 統(tǒng)計(jì)。
由函數(shù)生成流Stream 提供了兩個(gè)靜態(tài)方法來(lái)從函數(shù)生成流 iterate 和 gengrate。這兩個(gè)操作都可以創(chuàng)建無(wú)線流。
iterate
Stream.iterate(0, (Integer integer) -> integer + 2);
這會(huì)創(chuàng)建一個(gè)從0開始無(wú)限的偶數(shù)流。
gengrate
Stream.generate(Math::random);
這會(huì)創(chuàng)建一個(gè)0到1之間無(wú)限的隨機(jī)數(shù)。
這一章詳細(xì)講了流的使用場(chǎng)景和需要注意的地方,特別是和傳統(tǒng)的操作做對(duì)比,能更好地支持并行處理。
Java 8 實(shí)戰(zhàn) 第五章 使用流 讀書筆記
歡迎加入咖啡館的春天(338147322)。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/70209.html
摘要:在包下主要包括輸入輸出兩種流,每種輸入輸出流又可分為字節(jié)流和字符流兩大類。輸入輸出是從程序運(yùn)行所在的內(nèi)存的角度而言的。的輸入流主要由和作為基類,而輸出流主要由和作為基類。 本章主要參考和摘自瘋狂java講義上面的(java編程思想的后面看過后有新的內(nèi)容再補(bǔ)充進(jìn)去吧)?! ≥斎胼敵鍪撬谐绦蚨急匦璧牟糠帧褂幂斎霗C(jī)制允許程序讀取外部數(shù)據(jù)(包括磁盤、光盤等存儲(chǔ)設(shè)備上的數(shù)據(jù)和用戶輸入的...
摘要:一面試題及剖析今日面試題今天壹哥帶各位復(fù)習(xí)一塊可能會(huì)令初學(xué)者比較頭疼的內(nèi)容,起碼當(dāng)時(shí)讓我很有些頭疼的內(nèi)容,那就是流。在這里壹哥會(huì)從兩部分展開介紹流,即與流。除此之外盡量使用字節(jié)流。關(guān)閉此輸入流并釋放與流相關(guān)聯(lián)的任何系統(tǒng)資源。 一. 面試題及剖析 1. 今日面試題 今天 壹哥 帶各位復(fù)習(xí)一塊可...
摘要:分類一按操作方式類結(jié)構(gòu)字節(jié)流和字符流字節(jié)流以字節(jié)為單位,每次次讀入或讀出是位數(shù)據(jù)。該對(duì)象并不是流體系中的一員,其封裝了字節(jié)流,同時(shí)還封裝了一個(gè)緩沖區(qū)字符數(shù)組,通過內(nèi)部的指針來(lái)操作字符數(shù)組中的數(shù)據(jù)。 分類一:按操作方式(類結(jié)構(gòu)) 字節(jié)流和字符流: 字節(jié)流:以字節(jié)為單位,每次次讀入或讀出是8位數(shù)據(jù)??梢宰x任何類型數(shù)據(jù)。 字符流:以字符為單位,每次次讀入或讀出是16位數(shù)據(jù)。其只能讀取字符類...
摘要:是一個(gè)系統(tǒng)支持的所有字符的集合,包括各國(guó)家文字標(biāo)點(diǎn)符號(hào)圖形符號(hào)數(shù)字等字符集簡(jiǎn)體中文碼表。支持中國(guó)國(guó)內(nèi)少數(shù)民族的文字,同時(shí)支持繁體漢字以及日韓漢字等字符集為表達(dá)任意語(yǔ)言的任意字符而設(shè)計(jì),是業(yè)界的一種標(biāo)準(zhǔn),也稱為統(tǒng)一碼標(biāo)準(zhǔn)萬(wàn)國(guó)碼。 1 File1.1 File類的概述和構(gòu)造方法File: 它是文件和目錄路徑名的抽象...
摘要:當(dāng)使用節(jié)點(diǎn)流進(jìn)行輸入輸出時(shí),程序直接連接到實(shí)際的數(shù)據(jù)源,和時(shí)間的輸入輸出節(jié)點(diǎn)連接處理流則用于對(duì)一個(gè)已存在的流進(jìn)行連接或封裝,通過封裝后的流來(lái)實(shí)現(xiàn)數(shù)據(jù)讀寫功能,處理流也被稱為高級(jí)流。 文件的編碼 文本文件就是字節(jié)序列,可以是任意編碼形式。在中文操作系統(tǒng)上直接創(chuàng)建文本文件,則該文本文件只能識(shí)別ANSI編碼,其他編碼方式會(huì)產(chǎn)生亂碼 package imooc.io; import java...
摘要:的是實(shí)現(xiàn)輸入輸出的基礎(chǔ)中把不同的輸入輸出源鍵盤文件網(wǎng)絡(luò)連接抽象的表述為流流的分類輸入流和輸出流按照流的流向來(lái)分輸入流只能從中讀數(shù)據(jù)而不能向其中寫數(shù)據(jù)輸出流只能向其中寫出數(shù)據(jù)而不能從中讀取數(shù)據(jù)此處的輸入輸出涉及到一個(gè)方向問題數(shù)據(jù)從內(nèi)存到硬盤被 Java的IO是實(shí)現(xiàn)輸入輸出的基礎(chǔ),Java中把不同的輸入/輸出源(鍵盤,文件,網(wǎng)絡(luò)連接)抽象的表述為流,stream. 流的分類 輸入流和輸...
閱讀 1418·2021-09-01 10:30
閱讀 2349·2021-07-23 10:38
閱讀 1051·2019-08-29 15:06
閱讀 3293·2019-08-29 13:53
閱讀 3420·2019-08-26 11:54
閱讀 1985·2019-08-26 11:38
閱讀 2515·2019-08-26 10:29
閱讀 3267·2019-08-23 18:15