国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

《Java8實戰》-第五章讀書筆記(使用流Stream-01)

OldPanda / 2703人閱讀

摘要:跳過元素流還支持方法,返回一個扔掉了前個元素的流。歸約到目前為止,我們見到過的終端操作都是返回一個之類的或對象等。這樣的查詢可以被歸類為歸約操作將流歸約成一個值。通過反復使用加法,你把一個數字列表歸約成了一個數字。

使用流

在上一篇的讀書筆記中,我們已經看到了流讓你從外部迭代轉向內部迭代。這樣,你就用不著寫下面這樣的代碼來顯式地管理數據集合的迭代(外部迭代)了:

/**
 * 菜單
 */
public static final List MENU =
        Arrays.asList(new Dish("pork", false, 800, Dish.Type.MEAT),
                new Dish("beef", false, 700, Dish.Type.MEAT),
                new Dish("chicken", false, 400, Dish.Type.MEAT),
                new Dish("french fries", true, 530, Dish.Type.OTHER),
                new Dish("rice", true, 350, Dish.Type.OTHER),
                new Dish("season fruit", true, 120, Dish.Type.OTHER),
                new Dish("pizza", true, 550, Dish.Type.OTHER),
                new Dish("prawns", false, 400, Dish.Type.FISH),
                new Dish("salmon", false, 450, Dish.Type.FISH));
List menu = Dish.MENU;
List vegetarianDishes = new ArrayList<>();
for(Dish d: menu){
    if(d.isVegetarian()){
        vegetarianDishes.add(d);
    }
}

我們可以使用支持 filter 和 collect 操作的Stream API(內部迭代)管理對集合數據的迭代。
你只需要將篩選行為作為參數傳遞給 filter 方法就行了。

List vegetarianDishes =
                menu.stream()
                        .filter(Dish::isVegetarian)
                        .collect(toList());

這種處理數據的方式很有用,因為你讓StreamAPI管理如何處理數據。這樣StreamAPI就可以在背后進行多種優化。此外,使用內部迭代的話,StreamAPI可以決定并行運行你的代碼。這要是用外部迭代的話就辦不到了,因為你只能用單一線程挨個迭代。接下來,你將會看到StreamAPI支持的許多操作。這些操作能讓你快速完成復雜的數據查詢,如篩選、切片、映射、查找、匹配和歸約。

切片和篩選

我們來看看如何選擇流中的元素:用謂詞篩選,篩選出各不相同的元素,忽略流中的頭幾個元素,或將流截短至指定長度。

用謂詞篩選

Streams 接口支持 filter方法(你現在應該很熟悉了)。該操作會接受一個謂詞(一個返回boolean 的函數)作為參數,并返回一個包括所有符合謂詞的元素的流。

List vegetarianDishes =
                menu.stream()
                        // 方法引用檢查菜肴是否適合素食者
                        .filter(Dish::isVegetarian)
                        .collect(toList());

篩選各異的元素

流還支持一個叫作 distinct 的方法,它會返回一個元素各異(根據流所生成元素的hashCode 和 equals 方法實現)的流。例如,以下代碼會篩選出列表中所有的偶數,并確保沒有重復。

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

首先是篩選出偶數,然后檢查是否有重復,最后打印。

截短流

流支持 limit(n) 方法,該方法會返回一個不超過給定長度的流。所需的長度作為參數傳遞
給 limit 。如果流是有序的,則最多會返回前 n 個元素。比如,你可以建立一個 List ,選出熱量超過300卡路里的頭三道菜:

List dishes = menu.stream()
                .filter(d -> d.getCalories() > 300)
                .limit(3)
                .collect(toList());
// pork beef chicken
dishes.forEach(dish -> System.out.println(dish.getName()));

上面的代碼展示了filter和limit的組合。我們可以看到,該方法之篩選出來了符合謂詞的頭三個元素,然后就立即返回了結果。請注意limit也可以放在無序流上比如源是一個 Set 。這種情況下, limit 的結果不會以任何順序排列。

跳過元素

流還支持 skip(n) 方法,返回一個扔掉了前n個元素的流。如果流中元素不足n個,則返回一個空流。請注意,limit(n)和skip(n)是互補的!例如,下面的代碼將跳過超過300卡路里的頭兩道菜,并返回剩下的。

List dishes = menu.stream()
                .filter(d -> d.getCalories() > 300)
                // 跳過前兩個
                .skip(2)
                .collect(toList());
// chicken french fries rice pizza prawns salmon
dishes.forEach(dish -> System.out.println(dish.getName()));
映射

一個非常常見的數據處理套路就是從某些對象中選擇信息。比如在SQL里,你可以從表中選擇一列。Stream API也通過 map 和 flatMap 方法提供了類似的工具。

對流中每一個元素應用函數

流支持 map 方法,它會接受一個函數作為參數。這個函數會被應用到每個元素上,并將其映
射成一個新的元素(使用映射一詞,是因為它和轉換類似,但其中的細微差別在于它是“創建一
個新版本”而不是去“修改”)。例如,下面的代碼把方法引用 Dish::getName 傳給了 map 方法,來提取流中菜肴的名稱:

List dishNames = menu.stream()
                .map(Dish::getName)
                .collect(toList());
// [pork, beef, chicken, french fries, rice, season fruit, pizza, prawns, salmon]
System.out.println(dishNames);

getName方法返回的是一個String,所以map方法輸出的流類型就是Stream。當然,我們也可以獲取通過map獲取其他的屬性。比如:我需要知道這個菜單的名字有多長,那么我們可以這樣做:

List len = menu.stream()
                .map(dish -> dish.getName().length())
                .collect(toList());
// [4, 4, 7, 12, 4, 12, 5, 6, 6]
System.out.println(len);

是的,就是這么簡單,當我們只需要獲取某個對象中的某個屬性時,通過map就可以實現了。

流的扁平化

你已經看到如何使用 map方法返回列表中每個菜單名稱的長度了。讓我們拓展一下:對于一張單詞 表 , 如 何 返 回 一 張 列 表 , 列 出 里 面 各 不 相 同 的 字 符 呢 ? 例 如 , 給 定 單 詞 列 表["Hello","World"] ,你想要返回列表 ["H","e","l", "o","W","r","d"] 。

你可能馬上會想到,將每個單詞映射成一張字符表,然后調用distance 來過濾重復的字符。

List words = Arrays.asList("Hello", "World");
List wordList = words.stream()
        .map(word -> word.split(""))
        .distinct()
        .collect(Collectors.toList());
wordList.forEach(wordArray -> {
    for (String s : wordArray) {
        System.out.print(s);
    }
    System.out.println();
});

執行結果:

Hello
World

執行完后一看,不對呀。仔細想一想:我們把["Hello", "World"]這兩個單詞把它們分割稱為了字符數組,["H", "e", "l", "l", "o"],["W", "o", "r", "l", "d"]。然后將這個字符數組去判斷是否重復,不是一個字符是否重復,而是這一個字符數組是否有重復。所以,打印出來就是Hello World。

幸好可以用flatMap來解決這個問題!讓我們一步步地來解決它。

嘗試使用 map 和 Arrays.stream()

首先,我們需要一個字符流,而不是數組流。有一個叫作Arrays.stream()的方法可以接受
一個數組并產生一個流,例如:
String[] arrayOfWords = {"Hello", "World"};
Stream streamOfwords = Arrays.stream(arrayOfWords);
按照剛剛上面的做法,使用map和Arrays.stream(),顯然是不行的。
這是因為,你現在得到的是一個流的列表(更準確地說是Stream)!的確,
你先是把每個單詞轉換成一個字母數組,然后把每個數組變成了一個獨立的流。

使用 flatMap

我們可以像下面這樣使用flatMap來解決這個問題:
String[] arrayOfWords = {"Hello", "World"};
Stream streamOfwords = Arrays.stream(arrayOfWords);
List uniqueCharacters = streamOfwords
        // 將每個單詞轉換為由其字母構成的數組
        .map(w -> w.split(""))
        // 將各個生成流扁平化為單個流
        .flatMap(Arrays::stream)
        .distinct()
        .collect(Collectors.toList());
// HeloWrd
uniqueCharacters.forEach(System.out::print);

太棒了,實現了我們想要的效果!使用flatMap方法的效果是,各個數組并不是分別映射成為一個流,而是映射成流的內容。所有使用map(s -> split(""))時生成的單個流都被合并起來,即扁平化為一個流。一言以蔽之, flatMap 方法讓你把一個流中的每個值都換成另一個流,然后把所有的流連接起來成為一個流。

查找和匹配

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

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

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

if(menu.stream().anyMatch(Dish::isVegetarian)){
    System.out.println("有素菜,不用擔心!");
}

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

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

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

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

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

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

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

查找元素

findAny方法返回當前流中的任意元素。它可以與其他流結合操作使用。比如,你可能想找到一道素食菜肴。我們可以使用filter和findAny來實現:

Optional dish = menu.stream()
                .filter(Dish::isVegetarian)
                .findAny();

OK,這樣就完成我們想要的了。但是,你會發現它返回的是一個Optional。Optional類(java.util.Optional)是一個容器類,代表一個值存在或者不存在。在上面的代碼中,findAny可能什么都沒找到。。Java 8的庫設計人員引入了 Optional ,這
樣就不用返回眾所周知容易出問題的 null 了。很好的解決了“十億美元的錯誤”!不過我們現在不討論它,以后再去詳細的了解它是如何的使用。

查找第一個元素

有些流有一個出現順序(encounter order)來指定流中項目出現的邏輯順序(比如由 List 或
排序好的數據列生成的流)。對于這種流,你可能想要找到第一個元素。為此有一個 findFirst
方法,它的工作方式類似于 findany 。例如,給定一個數字列表,下面的代碼能找出第一個平方
能被3整除的數:

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

是的,通過鏈式調用,就完成了我們想要的功能,比起以前來說好太多了。你可能有一個疑問,findAny和findFrist在什么時候使用比較好或者說兩個都存在怎么辦。findAny和findFrist是并行的。找到第一個元素在并行上限制的更多。如果,你不關心放回元素是哪一個,請使用findAny,因為它在使用并行流時限制比較少。

歸約

到目前為止,我們見到過的終端操作都是返回一個 boolean ( allMatch 之類的)、 void
( forEach )或 Optional 對象( findAny 等)。你也見過了使用 collect 來將流中的所有元素組合成一個 List 。接下來,我們將會看到如何把一個流中的元素組合起來,使用reduce操作來表達更復雜的查詢,比如“計算菜單中的總卡路里”或者“菜單中卡路里最高的菜是哪一個”。此類查詢需要將流中的所有元素反復結合起來,得到一個值,比如一個Integer。這樣的查詢可以被歸類為歸約操作(將流歸約成一個值)。用函數式編程語言的術語來說,這稱為折疊(fold),因為你可以將這個操作看成把一張長長的紙(你的流)反復折疊成一個小方塊,而這就是折疊操作的結果。

元素求和

在沒有reduce之前,我們先用foreach循環來對數字列表中的元素求和:

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

numbers 中的每個元素都用加法運算符反復迭代來得到結果。通過反復使用加法,你把一個
數字列表歸約成了一個數字。

要是還能把所有的數字相乘,而不必去復制粘貼這段代碼,豈不是很好?這正是 reduce 操
作的用武之地,它對這種重復應用的模式做了抽象。你可以像下面這樣對流中所有的元素求和:

List numbers = Arrays.asList(3, 4, 5, 1, 2);
int sum = numbers.stream().reduce(0, (a, b) -> a + b);
// 15
System.out.println(sum);

我們很簡單的就完成了元素與元素相加最后得到的結果。如果是元素與元素相乘,也很簡單:

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

是的,就是這么簡單!我們還可以使用方法引用來簡化求和的代碼,讓它看起來更加簡潔:

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

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

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

為什么它返回一個 Optional 呢?考慮流中沒有任何元素的情況。 reduce 操作無
法返回其和,因為它沒有初始值。這就是為什么結果被包裹在一個 Optional 對象里,以表明和
可能不存在。現在看看用 reduce 還能做什么。

最大值和最小值

原來,只要用歸約就可以計算最大值和最小值了!讓我們來看看如何利用剛剛學到的 reduce
來計算流中最大或最小的元素。

Optional max = numbers.stream().reduce(Integer::max);

reduce 操作會考慮新值和流中下一個元素,并產生一個新的最大值,直到整個流消耗完!就像這樣:

3 - 4 - 5 - 1 - 2
↓
3 → 4
    ↓
    4 → 5
        ↓
        5 → 1
            ↓
            5 → 2
                ↓
                5

通過這樣的形式去比較哪個數值是最大的!如果,你獲取最小的數值,也很簡單只需要這樣:

Optional min = numbers.stream().reduce(Integer::min);

好了,關于流的使用就想講到這了,在下一節中我們將會付諸實戰,而不是看完了之后不去使用它,相信過不了多久我們就會忘記的!

小結

這一章的讀書筆記中,我們學習和了解到了:

Streams API可以表達復雜的數據處理查詢。

你可以使用 filter 、 distinct 、 skip 和 limit 對流做篩選和切片。

你可以使用 map 和 flatMap 提取或轉換流中的元素。

你可以使用 findFirst 和 findAny 方法查找流中的元素。你可以用 allMatch、noneMatch 和 anyMatch 方法讓流匹配給定的謂詞。

這些方法都利用了短路:找到結果就立即停止計算;沒有必要處理整個流。

你可以利用 reduce 方法將流中所有的元素迭代合并成一個結果,例如求和或查找最大

元素。

代碼

Github: chap5

Gitee: chap5

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/76968.html

相關文章

  • Java8實戰》-五章讀書筆記使用Stream-02)

    摘要:第三個問題查找所有來自于劍橋的交易員,并按姓名排序。第六個問題打印生活在劍橋的交易員的所有交易額。第八個問題找到交易額最小的交易。 付諸實戰 在本節中,我們會將迄今學到的關于流的知識付諸實踐。我們來看一個不同的領域:執行交易的交易員。你的經理讓你為八個查詢找到答案。 找出2011年發生的所有交易,并按交易額排序(從低到高)。 交易員都在哪些不同的城市工作過? 查找所有來自于劍橋的交易...

    liangzai_cool 評論0 收藏0
  • 《java 8 實戰讀書筆記 -五章 使用

    摘要:比如,你可以建立一個,選出熱量超過卡路里的頭三道菜請注意也可以用在無序流上,比如源是一個。跳過元素流還支持方法,返回一個扔掉了前個元素的流。一般來說,應該使用來對這種流加以限制,以避免打印無窮多個值。 一、篩選和切片 1.用謂詞篩選 Streams接口支持filter方法。該操作會接受一個謂詞(一個返回boolean的函數)作為參數,并返回一個包括所有符合謂詞的元素的流。例如篩選出所有...

    Richard_Gao 評論0 收藏0
  • Java8實戰》-讀書筆記第一章(02)

    摘要:實戰讀書筆記第一章從方法傳遞到接著上次的,繼續來了解一下,如果繼續簡化代碼。去掉并且生成的數字是萬,所消耗的時間循序流并行流至于為什么有時候并行流效率比循序流還低,這個以后的文章會解釋。 《Java8實戰》-讀書筆記第一章(02) 從方法傳遞到Lambda 接著上次的Predicate,繼續來了解一下,如果繼續簡化代碼。 把方法作為值來傳遞雖然很有用,但是要是有很多類似與isHeavy...

    lushan 評論0 收藏0
  • Java8實戰》-第四章讀書筆記(引入Stream

    摘要:內部迭代與使用迭代器顯式迭代的集合不同,流的迭代操作是在背后進行的。流只能遍歷一次請注意,和迭代器類似,流只能遍歷一次。 流(Stream) 流是什么 流是Java API的新成員,它允許你以聲明性方式處理數據集合(通過查詢語句來表達,而不是臨時編寫一個實現)。就現在來說,你可以把它們看成遍歷數據集的高級迭代器。此外,流還可以透明地并行處理,你無需寫任何多線程代碼了!我會在后面的筆記中...

    _ivan 評論0 收藏0
  • Java8實戰》-第六章讀書筆記(用收集數據-02)

    摘要:使用流收集數據分區分區是分組的特殊情況由一個謂詞返回一個布爾值的函數作為分類函數,它稱分區函數。這種情況下,累加器對象將會直接用作歸約過程的最終結果。這也意味著,將累加器不加檢查地轉換為結果是安全的。 使用流收集數據 分區 分區是分組的特殊情況:由一個謂詞(返回一個布爾值的函數)作為分類函數,它稱分區函數。分區函數返回一個布爾值,這意味著得到的分組 Map 的鍵類型是 Boolean ...

    jcc 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<