天天看點

java8實戰四:使用流--Stream

使用流

在本章中,你将會看到許多Stream API支援的許多操作.這些操作能讓你快速完成許多複雜的查詢.如篩選、切片、映射、查找、比對和歸約。

接下來,我們會看到一些特殊的流:數值流,來自檔案和數組等多種來源的流,最後是無限流.

1 篩選和切片

在本節中,我們來看看如何選擇流中的元素:用謂詞篩選,篩選出各不相同的元素,忽略流

中的頭幾個元素,或将流截短至指定長度。

1.1 用謂詞篩選

filter()方法:

該操作會接受一個謂詞作為參數,并傳回一個包含所有符合謂詞元素的流.

例如,你可以像下圖所示的這樣,篩選出所有素菜,建立一張素食菜單:

@Test
    public void test2() {

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

1.2 篩選各異的元素

distinct()方法:

它會傳回一個元素各異的流(根據流所生成元素的hashCode 和 equals 方法實作),

例如,以下代碼會篩選出清單中所有的偶數,并確定沒有重複。

@Test
    public void test3() {
        List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);

       numbers.stream()
                .filter(i -> i % 2 == 0)
                .distinct()
                .forEach(System.out::println);


    }      

1.3 截短流

limit()方法:

該方法會傳回一個不超過給定長度的流。所需的長度作為參數傳遞

給 limit 。如果流是有序的,則最多會傳回前 n 個元素。

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

你可以看到,該方法隻選出了符合謂詞的頭三個元素,

然後就立即傳回了結果。

請注意 limit 也可以用在無序流上,比如源是一個 Set 。這種情況下, limit 的結果不會以

任何順序排列

1.4 跳過元素

流還支援 skip(n) 方法,傳回一個扔掉了前 n 個元素的流。如果流中元素不足 n 個,則傳回一

個空流。請注意, limit(n) 和 skip(n) 是互補的!

@Test
    public void test4() {

        List<Dish> vegetarianMenu = menu.stream().filter(dish -> dish.getCalories() > 300)
                .skip(2)
                .collect(toList());

        System.out.println(vegetarianMenu);
    }      

2 映射

一個非常常見的資料處理套路就是從某些對象中選擇資訊.比如在sql中,你可以選擇一列.

Stream API也通過 map()和flatMap()方法提供了類似的工具.

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

map()

它會接受一個函數作為參數,這個函數會被應用到每個元素上.并将其映射成一個新的元素.

例如,下面的代碼把方法引用 Dish::getName 傳給了 map 方法,

來提取流中菜肴的名稱:

@Test
    public void test5() {

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

        System.out.println(vegetarianMenu);
    }      

因為 getName 方法傳回一個 String ,是以 map 方法輸出的流的類型就是 Stream 。

讓我們看一個稍微不同的例子來鞏固一下對 map 的了解。給定一個單詞清單,你想要傳回另

一個清單,顯示每個單詞中有幾個字母。怎麼做呢?你需要對清單中的每個元素應用一個函數。

這聽起來正好該用 map 方法去做!

@Test
    public void test6() {

        List<String> words = Arrays.asList("Java 8", "Lambdas", "In", "Action");
        List<Integer> integers = words.stream()
                .map(String::length)
                .collect(toList());

        System.out.println(integers);
    }      

結果:

[6, 7, 2, 6]      

現在讓我們回到提取菜名的例子。如果你要找出每道菜的名稱有多長,怎麼做?你可以像下

面這樣,再連結上一個 map :

@Test
    public void test7() {

       List<Integer> integers = menu.stream()
               .map(Dish::getName)
               .map(String::length)
               .collect(toList());
       System.out.println(integers);
   }      

結果:

[4, 4, 7, 12, 4, 12, 5, 6, 6]      

2.2 流的扁平化

你已經看到如何使用 map 方法傳回清單中每個單詞的長度了。讓我們拓展一下:對于一張單

詞 表 , 如 何 返 回 一 張 列 表 , 列 出 裡 面 各 不 相 同 的 字 符 呢 ? 例 如 , 給 定 單 詞 列 表

[“Hello”,”World”] ,你想要傳回清單 [“H”,”e”,”l”, “o”,”W”,”r”,”d”] 。

你可能首先想到這樣做

@Test
    public void test9() {
       List<String> words = Arrays.asList("Hello","World");
       List<String[]> list = words.stream()
               .map(word -> word.split(""))
               .distinct()
               .collect(toList());

       System.out.println(list);

   }      

這個方法的問題在于,傳遞給 map 方法的Lambda為每個單詞傳回了一個 String[] ( String

清單)。是以, map 傳回的流實際上是 Stream

[[Ljava.lang.String;@1def03a, [Ljava.lang.String;@122cdd0]      

使用 flatMap()

@Test
    public void test11() {
       List<String> words = Arrays.asList("Hello","World");
       List<String> list = words.stream()
               .map(word -> word.split(""))
               .flatMap(Arrays::stream)
               .distinct()
               .collect(toList());

       System.out.println(list);

   }      

結果:

[H, e, l, o, W, r, d]      

要想搞清楚 flatMap() 的效果,我們首先來看看 Arrays.stream() 方法

@Test
    public void test10() {
       List<String> words = Arrays.asList("Hello","World");
       List<Stream<String>> list = words.stream()
               .map(word -> word.split(""))
               .map(Arrays::stream)
               .distinct()
               .collect(toList());

       System.out.println(list);

   }      

結果:

[java.util.stream.ReferencePipeline$Head@1591d15, java.util.stream.ReferencePipeline$Head@1ae6ba4]      

可以看出,Arrays.stream() 的方法可以接受一個數組并産生一個流.

而 flatMap(Arrays::stream) 的效果就是 把兩個流合并起來,即扁平化為一個流.

flatMap() 的參數是 Stream類型.

super T, ? extends R> mapper);

   <R> Stream<R> flatMap(Function<? super      

3 查找和比對

另一個常見的資料處理套路是看看資料集中的某些元素是否比對一個給定的屬性。Stream

API通過 allMatch 、 anyMatch 、 noneMatch 、 findFirst 和 findAny 方法提供了這樣的工具

3.1 檢查謂詞是否至少比對一個元素

anyMatch()

@Test
    public void test12() {
       boolean b = menu.stream().anyMatch(Dish::isVegetarian);
       if(b){
           System.out.println("有素菜!");

       }

   }      

anyMatch 方法可以回答“流中是否有一個元素能比對給定的謂詞”。

anyMatch 方法傳回一個 boolean ,是以是一個終端操作.

3.2 檢查謂詞是否比對所有元素

allMatch()

@Test
    public void test13() {
        boolean b = menu.stream()
                .allMatch(dish -> dish.getCalories() < 1000);
        if (b) {
            System.out.println("沒有高熱量事物,吃!");
        }

    }      

noneMatch()

和 allMatch 相對的是 noneMatch 。它可以確定流中沒有任何元素與給定的謂詞比對

@Test
    public void test14() {
        boolean b = menu.stream().noneMatch(dish -> {
            return dish.getCalories() > 1000;
        });
        if (b) {
            System.out.println("沒有高熱量事物,吃!");
        }

    }      
短路求值

有些操作不需要處理整個流就能得到結果。例如,假設你需要對一個用 and 連起來的大布

爾表達式求值。不管表達式有多長,你隻需找到一個表達式為 false ,就可以推斷整個表達式

将傳回 false ,是以用不着計算整個表達式。這就是 短路。

對于流而言,某些操作(例如 allMatch 、 anyMatch 、 noneMatch 、 findFirst 和 findAny )

不用處理整個流就能得到結果。隻要找到一個元素,就可以有結果了。同樣, limit 也是一個

短路操作:它隻需要建立一個給定大小的流,而用不着處理流中所有的元素。在碰到無限大小

的流的時候,這種操作就有用了:它們可以把無限流變成有限流.

3.3 查找元素

findAny()

@Test
    public void test15() {
        Optional<Dish> any = menu.stream()
                .filter(dish -> dish.getCalories() == 450)
                .findAny();
        System.out.println(any);

       any.ifPresent(dish -> {
            System.out.println(dish.getCalories());
        });

    }      

結果:

Optional[Dish{name='salmon', vegetarian=false, calories=450, type=FISH}]
450      

可以看到 findAny() 傳回一個 Optional 類型的值,

Optional 簡介

Optional 類( java.util.Optional )是一個容器類,代表一個值存在或不存在。在

上面的代碼中, findAny 可能什麼元素都沒找到。Java 8的庫設計人員引入了 Optional ,這

樣就不用傳回衆所周知容易出問題的 null 了。

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

* isPresent() 将在 Optional 包含值的時候傳回 true , 否則傳回 false 。

* ifPresent(Consumer block) 會在值存在的時候執行給定的代碼塊。我們在第3章

介紹了 Consumer 函數式接口;它讓你傳遞一個接收 T 類型參數,并傳回 void 的Lambda

表達式。

* T get() 會在值存在時傳回值,否則抛出一個 NoSuchElement 異常。

* T orElse(T other) 會在值存在時傳回值,否則傳回一個預設值。

3.4 查找第一個元素

FindFirst()

用來找到流中的第一個元素,它的工作方式類似于 findany

@Test
    public void test16() {
        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(); // 9      

結果:

Optional[9]      
何時使用 findFirst 和 findAny????

**你可能會想,為什麼會同時有 findFirst 和 findAny 呢?答案是并行。找到第一個元素

在并行上限制更多。如果你不關心傳回的元素是哪個,請使用 findAny ,因為它在使用并行流

時限制較少。**

4 歸約

到目前為止,你所見到的終端操作都傳回一個boolean(allMatch之類),void(forEach),或Optional對象(findOny等).你也見過了可以用collect來将流中的元素合成一個List.

接下來,你将會看到如何把一個流中的元素組合起來,比如“計算菜單中的總卡路裡”或“菜單中卡路裡最高的菜是哪一個”.

此類查詢需要将流反複組合起來,得到一個值比如Integer,這樣的查詢可被歸類為 歸約 操作.

用函數式程式設計的術語來說,這稱為折疊(fold),因為你可以講這個流看作一張長長的紙(你的流),反複折疊成一個小方塊,這就是折疊操作的結果.

4.1 元素求和

reduce()

先看使用java8之前的 for-each 的做法

int sum = 0;
for (int      

numbers中的每一個元素都使用加法運算反複疊代來得到結果.通過反複使用加法,你把一個數字清單歸約成了一個數字.

要是還能把所有的數字相乘,而不必去複制粘貼這段代碼,豈不是很好?這正是 reduce 操

作的用武之地,它對這種重複應用的模式做了抽象。你可以像下面這樣對流中所有的元素求和

@Test
    public void test17() {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        Integer reduce = numbers.stream().reduce(0, (a, b) -> a + b);

        System.out.println(reduce);//15      

reduce 接受兩個參數:

* 一個初始值,這裡是0

* 一個 BinaryOperator 來将兩個元素結合起來産生一個新值,這裡我們用的是

lambda (a, b) -> a + b

上面的操作效果我們可以了解為:把初始值和後面的函數每一次計算的結果相加求和.

@Test
    public void test17() {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        Integer reduce = numbers.stream()
                .reduce(1, (a,b)->a*b);
        System.out.println(reduce);//120      

這個操作效果我們可以了解為:把初始值和後面的函數每一次計算的結果相乘.

通過這兩個例子我們可以看到:reduce兩個參數的運算方式取決于後面Lambda的運算方式

Lambda反複結合每個元素,直到流被歸約成一個值.

在java8中Integer類多了幾個靜态方法,

/**
     * Adds two integers together as per the + operator.
     *
     * @param a the first operand
     * @param b the second operand
     * @return the sum of {@code a} and {@code b}
     * @see java.util.function.BinaryOperator
     * @since
    public static int sum(int a, int b) {
        return a + b;
    }

    /**
     * Returns the greater of two {@code int} values
     * as if by calling {@link Math#max(int, int) Math.max}.
     *
     * @param a the first operand
     * @param b the second operand
     * @return the greater of {@code a} and {@code b}
     * @see java.util.function.BinaryOperator
     * @since
    public static int max(int a, int b) {
        return Math.max(a, b);
    }

    /**
     * Returns the smaller of two {@code int} values
     * as if by calling {@link Math#min(int, int) Math.min}.
     *
     * @param a the first operand
     * @param b the second operand
     * @return the smaller of {@code a} and {@code b}
     * @see java.util.function.BinaryOperator
     * @since
    public static int min(int a, int b) {
        return      

這不正好可以讓我們結合Lambda表達式嗎!(可能java8的設計者特意增加了這幾個操作适應Lambda程式設計需要),總之我們代碼看起來可以更簡潔

@Test
    public void test17() {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        Integer reduce = numbers.stream()
                .reduce(0, Integer::sum);
        System.out.println(reduce);

    }      

無初始值

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

@Test
    public void test18() {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        Optional<Integer> optional = numbers.stream()
                .reduce(Integer::sum);
        System.out.println(optional);

    }      

為什麼它傳回一個 Optional 呢?考慮到流中沒有值的情況,reduce操作便無法傳回其和,因為沒有初始值.這就是為什麼結果被包裹在一個Optional對象裡,以表明結果可能不存在.

4.2 求最大值和最小值

@Test
    public void test19() {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        Optional<Integer> max = numbers.stream()
                .reduce(Integer::max);
        Optional<Integer> min = numbers.stream().reduce(Integer::min);

        System.out.println("max="+max+",min="+min);

    }      

給定兩個元素能夠傳回最大值的Lambda,redce會考慮流中的新值和下一個元素,并産生一個新的最大值,直到整個流消耗完.

歸約方法的優勢與并行化

相比于java8之前的疊代求和,我們使用reduce的好處在于,這裡的疊代被内部疊代抽象掉了,這讓内部疊代可以選擇并行執行reduce操作. 而疊代求和的例子要更新共享變量sum,這不是那麼容易并行化的.

如果你加入了同步,可能會發現線程競争抵消掉了并行本應該帶來的性能提升.

這種計算的并行化需要另一種方法:将輸入分塊,再分塊求和,最後再合并起來.

這正式reduce所提供的,使用流來對所有的元素并行求和時,你的代碼幾乎不用修改: stream() 換成了 parallelStream().

付諸實踐

我們先會議一下目前為止講到的所有流操作

public class TradeTest


    Trader raoul = new Trader("Raoul", "Cambridge");
    Trader mario = new Trader("Mario", "Milan");
    Trader alan = new Trader("Alan", "Cambridge");
    Trader brian = new Trader("Brian", "Cambridge");

    List<Transaction> transactions = Arrays.asList(
            new Transaction(brian, 2011, 300),
            new Transaction(raoul, 2012, 1000),
            new Transaction(raoul, 2011, 400),
            new Transaction(mario, 2012, 710),
            new Transaction(mario, 2012, 700),
            new Transaction(alan, 2012, 950)
    );


    //(1) 找出2011年發生的所有交易,并按交易額排序(從低到高)。
    @Test
    public  void test1(){

        List<Transaction> list = transactions.stream()
                .filter(transaction -> transaction.getYear() == 2011)
                .sorted(Comparator.comparing(Transaction::getValue))
                .collect(toList());

        System.out.println(list);

    }

    //(2) 交易員都在哪些不同的城市工作過?
    @Test
    public void test2(){

        List<String> list = transactions.stream()
                .map(Transaction::getTrader)
                .map(Trader::getCity)
                .distinct()
                .collect(toList());
        System.out.println(list);

        List<String> list1 = transactions.stream()
                .map(transaction -> transaction.getTrader().getCity())
                .distinct()
                .collect(toList());
        System.out.println(list1);


    }

    //(3) 查找所有來自于劍橋的交易員,并按姓名排序。
    @Test
    public void test3(){
        List<Trader> list = transactions.stream()
                .map(Transaction::getTrader)
                .filter(trader -> trader.getCity().equals("Cambridge"))
                .sorted(Comparator.comparing(Trader::getName))
                .collect(toList());
        System.out.println(list);

        List<Trader> list1 = transactions.stream()
                .filter(transaction -> transaction.getTrader().getCity().equals("Cambridge"))
                .map(Transaction::getTrader)
                .sorted(Comparator.comparing(Trader::getName))
                .collect(toList());

        System.out.println(list1);


    }

    //(4) 傳回所有交易員的姓名字元串,按字母順序排序。
    @Test
    public void test4(){
        List<String> list = transactions.stream()
                .map(Transaction::getTrader)
                .map(Trader::getName)
                .sorted((s1, s2) -> s1.compareTo(s2))
                //.distinct()
                .collect(toList());
        System.out.println(list);


        List<String> list1 = transactions.stream()
                .map(transaction -> transaction.getTrader().getName())
                .sorted((s1, s2) -> s1.compareTo(s2))
                .collect(toList());
        System.out.println(list1);


    }

    //(5) 有沒有交易員是在米蘭工作的?
    @Test
    public void test5(){
        boolean b = transactions.stream()
                .map(Transaction::getTrader)
                .anyMatch(trader -> trader.getCity().equals("Milan"));
        System.out.println(" 有沒有交易員是在米蘭工作的?="+b);

        boolean b1 = transactions.stream()
                .anyMatch(transaction -> transaction.getTrader().getCity().equals("Milan"));
        System.out.println(" 有沒有交易員是在米蘭工作的?="+b1);


    }

    //(6) 列印生活在劍橋的交易員的所有交易額。
    @Test
    public void test6(){
        List<Integer> list = transactions.stream()
                .filter(transaction -> transaction.getTrader().getCity().equals("Cambridge"))
                .map(Transaction::getValue)
                .collect(toList());
        System.out.println(list);
    }

    // (7) 所有交易中,最高的交易額是多少?
    @Test
    public void test7(){
        Optional<Integer> reduce = transactions.stream()
                .map(Transaction::getValue)
                .reduce(Integer::max);
        System.out.println(reduce);

    }

    //(8) 找到交易額最小的交易。
    @Test
    public void test8(){
        Optional<Integer> reduce = transactions.stream()
                .map(Transaction::getValue)
                .reduce(Integer::min);
        System.out.println(reduce);
    }
     //(9) 計算交易總額
        @Test
        public void test9(){
            Integer reduce = transactions.stream()
                    .map(transaction -> transaction.getValue())
                    .reduce(0, Integer::sum);
            System.out.println(reduce);
        }


}      

5 數值流

我們先看下這段代碼有什麼問題

//(9) 計算交易總額
    @Test
    public void test9(){
        Integer reduce = transactions.stream()
                .map(transaction -> transaction.getValue())
                .reduce(0, Integer::sum);
        System.out.println(reduce);
    }      

這段代碼的問題是,它有一個暗含的裝箱成本。每個 Integer 都必須拆箱成一個原始類型,

再進行求和

Java 8引入了三個原始類型特化流接口來解決這個問題:

5.1 原始類型流特化

IntStream,DoubleStream,LongStream 分别将流中的元素特化為 int ,double, long ,進而避免了暗含的裝箱成本.

每個接口都帶來了進行常用數值歸約的新方法,比如對數值流求和的 sum ,找到最大元素的 max 。

此外還有在必要時再把它們轉換回對象流的方法。

映射到數值流

将流轉化為特化版本的常用方法為 mapToInt(),mapToDouble(),mapToLong().

這些方法和前面說的 map 方法的工作方式一樣,隻是它們傳回的是一個特化流,而不是 Stream

@Test
    public void test20() {
        int sum = menu.stream() //傳回一個Stream<Dish>
                .mapToInt(Dish::getCalories) //傳回一個IntStream      

請注意,如果流是空的, sum 預設傳回 0 。 IntStream 還支援其他的友善方法,如max 、 min 、 average 等。

轉換回對象流 boxed()

同樣,一旦有了數值流,你可能會想把它轉換回非特化流。

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

預設值 OptionalInt

要找到 IntStream 中的最大元素,可以調用 max 方法,它會傳回一個 OptionalInt :

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

現在,如果沒有最大值的話,你就可以顯式處理 OptionalInt 去定義一個預設值了:

@Test
    public void test21() {
        OptionalInt optionalInt = menu.stream()
                .mapToInt(Dish::getCalories)
                .max();

        int max = optionalInt.orElse(0);


        System.out.println(max);

    }      

數值範圍

和數字打交道時,有一個常用的東西就是數值範圍。比如,假設你想要生成1和100之間的所有數字.

java8引入了兩個可用于 IntStream 和LongStream的靜态方法,range 和 rangeclosed.

這兩個方法都是第一個參數接受起始值,第二個參數接受結束值,但range是不包含結束值的, rangeclosed包含結束值

@Test
    public void test22() {

        IntStream intStream = menu.stream()
                .mapToInt(Dish::getCalories);

        IntStream intStream1 = IntStream.range(1, 100)
                .filter(n -> n % 2 == 0);
        System.out.println(intStream1.count());
    }      

6 建構流

本節将介紹如何從值序列、數組、檔案來建立流,甚至由生成函數來建立無限流!

6.1 由值建立流

Stream.of()

你可以通過靜态方法 Stream.of() 通過顯示值建立一個流,他可以接受任意數量的參數.

@Test
    public void test23() {

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

6.2 由數組建立流

Arrays.stream()

@Test
    public void test25() {

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

Arrays.stream() 有很多重載方法.

6.3 由檔案建立流

java中用于處理檔案等I/O操作的NIO API已經更新,以便利用Stream API.

java.nio.file.Files 中的很多靜态方法都會傳回一個流。例如,一個很有用的方法是

Files.lines ,它會傳回一個由指定檔案中的各行構成的字元串流.

統計一個檔案中有多少各不相同的詞:

@Test
    public void test26() {
        try {
            Stream<String> stream = Files.lines(Paths.get("data.txt"));
            //Arrays.stream()把字元串數組轉成流
            long count = stream.flatMap(line -> Arrays.stream(line.split(" ")))
                    .distinct()
                    .count();
            System.out.println(count);
        } catch      

你可以使用Files.lines得到一個流,其中每個元素都是給定檔案中的一行.

然後你可以對每一行進行split方法拆分單詞,應該注意的是:你應該如何使用 flatMap()産生一個扁平的單詞流.而不是給每一行生成一個單詞流.

6.4 由函數生成流:建立無限流

Stream API 提供了兩個靜态方法來從函數生成流: Stream.iterate和Stream.generate.

這兩個操作可以建立所謂的無限流:不像從固定集合建立的流那樣有固定大小的流。由 iterate

和 generate 産生的流會用給定的函數按需建立值,是以可以無窮無盡地計算下去!一般來說,

應該使用 limit(n) 來對這種流加以限制,以避免列印無窮多個值.

疊代 iterate

@Test
    public void test27() {
    Stream.iterate(0,n->n+2)
            .limit(10)
            .forEach(System.out::println);
    }      

iterate 方法接受一個初始值,還有一個作用于每次産生新值上的Lambda( UnaryOperator 類型),

這裡,我們使用Lambda n -> n + 2 ,傳回的是前一個元

素加上2。是以, iterate 方法生成了一個所有正偶數的流.

這種iterate基本上是順序的,因為結果取決于前一次應用,請注意,此操作将生成一個無限流——這個流沒有結尾,因為值是按需計算的,可以永遠計算下去。我們說這個流是無界的。

生成 generate

與 iterate 方法類似, generate 方法也可讓你按需生成一個無限流。但 generate 不是依次

對每個新生成的值應用函數的。它接受一個 Supplier 類型的Lambda提供新的值。我們先來

看一個簡單的用法:

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

小結:

  • Streams API可以表達複雜的資料處理查詢。常用的流操作總結在表5-1中
  • 你可以使用 filter ,distinct ,limit ,skip對流做篩選和切片.
  • 你可以使用map 和 flstMap 提取或者轉換流中的元素
  • 你可以使用 findFirst和findAny 查找流中的元素
  • 你可以使用anyMatch ,allMatch ,noneMatch 讓流比對給定的謂詞

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

  • 你可以使用 reduce 方法将流中的所有元素進行疊代合并成一個結果,例如求和或者查找最大元素
  • filter和map操作是無狀态的,他們并不存儲任何狀态.

    reduce 操作要存儲狀态才能計算出一個值,

    sorted和distinct也要存儲狀态,因為他們把流中所有元素緩存起來才能傳回一個新的流. 這種操作稱為有狀态操作.

  • 流有三種基本的原始類型特化: IntStream 、 DoubleStream 和 LongStream 。它們的操作也有相應的特化
  • 流不僅可以從集合建立,也可從值、數組、檔案以及 iterate 與 generate 等特定方法建立。
  • 無限流是沒有固定大小的流