1. 程式人生 > >Java8之Stream(3)

Java8之Stream(3)

四、Stream使用詳解

好了,背景知識介紹完成,並且我們在最開始也對Stream有了一個大致的瞭解,在本章中我們詳細的介紹一下Stream,每一個示例都會有相應的程式碼配合,讓讀者理解更加的透徹。

對 Stream 的使用就是實現一個 filter-map-reduce 過程,產生一個最終結果,或者導致一個副作用(side effect),當我們使用流的時候,通常會包括三個基本的步驟:

v 獲取資料

v 轉換資料,每次的轉換不會改變原有Stream而是會返回一個新的Stream,並且轉換可以有多次的轉換。

v 執行操作獲取想要的結果

 

4.1 如何獲得Stream

獲取流的方式有很多種,我們在這本節中,逐個的介紹,我將程式碼都放在junit測試程式碼中。

4.1.1 From Collections

    @Test

    public void fromCollections()

    {

        List<String> list = Collections.emptyList();

        Stream<String> stream = list.stream();

        Stream<String> parallelStream = list.parallelStream();

    }

4.1.2 From Arrays

    @Test

    public void fromArrays()

    {

        int[] array = {1,2,3,4,5};

        IntStream stream = Arrays.stream(array);

        Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5, 6);

    }

4.1.3 From Static Factory

    @Test

    public void fromArrays()

    {

        int[] array = {1,2,3,4,5};

        IntStream stream = Arrays.stream(array);

        Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5, 6);

    }

4.1.4 From Files

    @Test

    public void fromFiles()

    {

        Path path = Paths.get("");

        try {

            Stream<Path> stream = Files.walk(path);

        } catch (IOException e) {

            e.printStackTrace();

        }

    }

4.1.5 Build By Yourself

除了一些常見的方式獲取Stream之外,我們還可以自己構造如何獲取一個Stream,這種情形通常用於隨機數、常量的 Stream,或者需要前後元素間維持著某種狀態資訊的 Stream。把 Supplier 例項傳遞給 Stream.generate() 生成的 Stream,預設是序列(相對 parallel 而言)但無序的(相對 ordered 而言)。由於它是無限的,在管道中,必須利用 limit 之類的操作限制 Stream 大小。

@Test

    public void generateByYourself() {

        Random random = new Random(System.currentTimeMillis());

        Supplier<Integer> supplierRandom = random::nextInt;

        Stream.generate(supplierRandom).limit(10).forEach((e)->System.out.println(e));

    }

好,來個更加複雜的,我們自定義一個Supplier,來演示一下如何自己generate一個Stream,為了方便程式碼展示,我寫了兩個區域性內部類,都在函式內部定義的兩個類,程式碼如下所示:

@Test

    public void generateByExpandSupplier() {

        class Member {

            private int id;

            private String name;

            public Member(int id, String name) {

                Member.this.id = id;

                Member.this.name = name;

            }

            public int getId() {

                return id;

            }

            public String getName() {

                return name;

            }

        }

        class MemberSupplier implements Supplier<Member> {

            private int index = 0;

            private Random random = new Random(System.currentTimeMillis());

            @Override

            public Member get() {

                return new Member(index = random.nextInt(100), "Member#" + index);

            }

        }

        Stream.generate(new MemberSupplier()).limit(20).forEach((m) -> System.out.println(m.getId() + ":" + m.getName()));

    }

執行結果如下所示:

42:Member#42

30:Member#30

38:Member#38

12:Member#12

15:Member#15

...........

4.1.6 Others

v java.util.Spliterator

v Random.ints()

v BitSet.stream()

v Pattern.splitAsStream(java.lang.CharSequence)

v JarFile.stream()

4.2 Stream的操作分類

掌握瞭如何獲取Stream,我們本節中來看看如何對Stream進行操作,Stream的操作大致分為兩類

Intermediate:一個流可以後面跟隨零個或多個 intermediate 操作。其目的主要是開啟流,做出某種程度的資料對映/過濾,然後返回一個新的流,交給下一個操作使用。這類操作都是惰性化的(lazy),就是說,僅僅呼叫到這類方法,並沒有真正開始流的遍歷。

Terminal:一個流只能有一個 terminal 操作,當這個操作執行後,流就被使用“光”了,無法再被操作。所以這必定是流的最後一個操作。Terminal 操作的執行,才會真正開始流的遍歷,並且會生成一個結果,或者一個 side effect。

還有一種操作被稱為 short-circuiting。用以指:

對於一個 intermediate 操作,如果它接受的是一個無限大(infinite/unbounded)的 Stream,但返回一個有限的新 Stream。

對於一個 terminal 操作,如果它接受的是一個無限大的 Stream,但能在有限的時間計算出結果。

當操作一個無限大的 Stream,而又希望在有限時間內完成操作,則在管道內擁有一個 short-circuiting 操作是必要非充分條件。

4.3 Stream的操作實戰

瞭解了Stream的分類,本節中我們一起來實戰一下對其所涉及的每個方法進程式碼演示。

4.3.1 Intermediate操作

Intermediate的操作方法大致有如下幾個:

v map (mapToInt, flatMap 等)

v Filter

v Distinct

v Sorted

v Peek

v Limit

v Skip

v Parallel

v Sequential

v unordered

4.3.1.1 map

@Test

    public void testMap() {

        int[] array = {1, 2, 3, 4, 5};

        IntStream.of(array).map(e -> e * 10).forEach((e) -> System.out.println(e));

    }

上面的程式碼是對每一個數組的元素都增加了十倍並且打印出來。

4.3.1.2 filter

@Test

    public void testFilter() {

        Integer[] array = {1, 2, 3, 4, 5};

        List<Integer> result = Stream.of(array).filter(e -> e > 3).collect(Collectors.toList());

        assertEquals(2,result.size());

    }

上面的程式碼是過濾掉比3小的資料,並且形成了一個新的Integer列表。

4.3.1.3 Distinct

    @Test

    public void testDistinct() {

        Integer[] array = {1, 1, 2, 3, 4, 5, 6, 5};

        assertEquals(8, array.length);

        List<Integer> result = Stream.of(array).distinct().collect(Collectors.toList());

        assertEquals(6, result.size());

    }

上面的程式碼過濾掉了重複的元素。

4.3.1.4 Sorted

   @Test

    public void testSorted() {

        Integer[] array = {3,4,6,1,8,2};

        Stream.of(array).sorted().forEach(e->System.out.println(e));

    }

Sorted是對流中的元素進行升序排列,並且形成一個新的流,如果你想有自己的排序規則,請實現Compare介面並且傳給sorted。

4.3.1.5 Peek

    @Test

    public void testPeek() {

        Integer[] array = {3,4,6,1,8,2};

        List<Integer> result = Stream.of(array).filter(e -> e % 2 == 0).peek(e -> System.out.println(e)).collect(Collectors.toList());

        System.out.println(result);

    }

Peek有點類似於forEach,但是他不會中斷這個Stream,體會一下上面的程式碼。

4.3.1.6 Limit

     @Test

    public void testLimit() {

        Integer[] array = {3,4,6,1,8,2};

        Stream.of(array).limit(4).forEach(e -> System.out.println(e));

    }

只獲取流中的前四個元素,並且形成一個新的流返回。

4.3.1.7 Skip

    @Test

    public void testSkip() {

        Integer[] array = {3,4,6,1,8,2};

        Stream.of(array).skip(3).forEach(e -> System.out.println(e));

    }

Limit是保留前面的幾個元素形成一個新的,而skip則是跳過前面的幾個元素形成一個新的流。

4.3.1.8 Parallel

    @Test

    public void testParallel() {

        Integer[] array = {3,4,6,1,8,2};

        Stream.of(array).parallel().forEach(e -> System.out.println(e));

    }

產生一個平行計算的流並且返回。

4.3.1.9 Sequential

    @Test

    public void testDistinct() {

        Integer[] array = {1, 1, 2, 3, 4, 5, 6, 5};

        assertEquals(8, array.length);

        List<Integer> result = Stream.of(array).distinct().collect(Collectors.toList());

        assertEquals(6, result.size());

    }

和Parallel相反,返回一個序列執行的Stream,有可能返回的是自己(this)。

4.3.2 Terminal

我們之前說過了Terminal會中斷整個流的操作,Stream的Terminal操作有如下幾個,有些我們已經演示過了,就不給出程式碼佔用版面了。

v forEach

v forEachOrdered

v toArray

v Reduce

v Collect

v Min

v Max

v Count

v anyMatch

v allMatch

v noneMatch

v findFirst

v findAny

v iterator

4.3.2.1 toArray

    @Test

    public void testForEachOrdered() {

        Integer[] array = {1, 2, 3, 4, 5, 6};

        Object[] result = Stream.of(array).filter(e -> e > 4).toArray();

        assertEquals(2,result.length);

    }

返回一個新的陣列。

4.3.2.2 reduce

這個方法的主要作用是把 Stream 元素組合起來。它提供一個起始值(種子),然後依照運算規則(BinaryOperator),和前面 Stream 的第一個、第二個、第 n 個元素組合。從這個意義上說,字串拼接、數值的 sum、min、max、average 都是特殊的 reduce。例如 Stream 的 sum 就相當於

Integer sum = integers.reduce(0, (a, b) -> a+b); 或

Integer sum = integers.reduce(0, Integer::sum);

也有沒有起始值的情況,這時會把 Stream 的前面兩個元素組合起來,返回的是 Optional。

   @Test

    public void testReduce() {

        String reduceResult = Stream.of("W", "a", "n", "g").reduce("", String::concat);

        assertEquals("Wang",reduceResult);

    }

4.3.2.3 max,min,count,sum

比較簡單,不演示了,猜都能猜出來。

4.3.2.4 anyMatch

Stream中的元素只要有一個滿足條件則就返回true,否則返回false

   @Test

    public void testAnyMatch() {

        Integer[] array = {1, 2, 3, 4, 5};

        boolean result = Stream.of(array).anyMatch(e -> e > 3);

        assertTrue(result);

    }

4.3.2.5 allMatch

所有的都必須滿足條件

4.3.2.6 noneMatch

所有的都不滿足條件

4.3.2.7 findFirst

   @Test

    public void testFindFirst() {

        Integer[] array = {1, 2, 3, 4, 5};

        Integer result = Stream.of(array).findFirst().get();

        assertEquals(1,result.intValue());

    }

返回流中的第一個滿足條件的資料返回。

4.3.2.8vfindAny

和FindFirst一樣都是返回一個數據,但是他是返回任意的一個元素,所以之前我們的斷言方式有可能會失敗。v

  @Test

    public void testFindAny() {

        Integer[] array = {1, 2, 3, 4, 5};

        Integer result = Stream.of(array).findAny().get();

        System.out.println(result);

    }

4.3.2.9 iterator

  @Test

    public void testIterator() {

        Integer[] array = {1, 2, 3, 4, 5};

        Iterator<Integer> iterator = Stream.of(array).iterator();

    }

返回我們熟悉的迭代器Iterator,不做解釋,相信你會明白吧。