1. 程式人生 > >Java8新特性之Stream詳解一

Java8新特性之Stream詳解一

寫了Lambda表示式的部落格,怎麼能少了對Stream的學習呢!接下來就將我所理解的Stream分享給大家,歡迎指出不足之處...

一、為什麼要使用Stream?Stream是什麼?在Java中該怎麼用呢?

       (1)為什麼要使用Stream?

       Java 8 中的 Stream 是對集合(Collection)物件功能的增強,它專注於對集合物件進行各種非常便利、高效的聚合操作,或者大批量資料操作 (bulk data operation)。Stream API 藉助於同樣新出現的 Lambda 表示式,極大的提高程式設計效率和程式可讀性。

      (2) Stream是什麼?

      首先,Stream 不是集合元素,它不是資料結構並不儲存資料,它是有關演算法和計算的,它更像一個高階版本的 Iterator。原始版本的 Iterator,使用者只能顯式地一個一個遍歷元素並對其執行某些操作;高階版本的 Stream,使用者只要給出需要對其包含的元素執行什麼操作,比如 “過濾掉長度大於 10 的字串”、“獲取每個字串的首字母”等,Stream 會隱式地在內部進行遍歷,做出相應的資料轉換。

      Stream 就如同一個迭代器(Iterator),單向,不可往復,資料只能遍歷一次,遍歷過一次後即用盡了,就好比流水從面前流過,一去不復返。

    (3) Stream在Java中如何用呢?

     當我們使用Stream流時,一般包含三個步驟:獲取一個數據源(source)→  資料轉換 → 執行操作獲取想要的結果,每次轉換原有 Stream 物件不改變,返回一個新的 Stream 物件(可以有多次轉換),這就允許對其操作可以像鏈條一樣排列,變成一個管道。

    有多種方式產生一個流:

   ① 從Collection和陣列中產生一個流:Collection.stream()           Collection.parallelStream()     

                                                               Arrays.stream(T array)      Stream.of(元素)

   ② 從BufferedReader中產生一個流: Collection.parallelStream()

二、詳解流的使用:

(1) map的詳解:map 生成的是個 1:1 對映,每個輸入元素,都按照規則轉換成為另外一個元素。還有一些場景,是一對多對映關係的,這時需要 flatMap。

例1:將陣列中的字串轉為大寫,求平方數

    @Test
    public void mapTest(){
        //將陣列的字串轉為大寫
        String[] wordList = new String[]{"Java","Android","OS","IOS"};
        List<String> output = Arrays.stream(wordList).
                map(String::toUpperCase).
                collect(Collectors.toList());
        System.out.println(output);

        //平方數
        List<Integer> nums = Arrays.asList(1,2,3,4);
        List<Integer> squareNums = nums.stream().
                map(n -> n * n).
                collect(Collectors.toList());
        System.out.println(squareNums);
    }

注意:map(String::toUpperCase)這樣的寫法,這就是我們上篇部落格講的Lambda表示式的方法引用!誒,大家會不會覺得toUpperCase()這個方法不是String類中的靜態方法,為啥可以這樣呼叫呢?    

         map((str) -> str.toUpperCase()) 還原之前的Lambda表示式,toUpperCase()是String的成員方法,但是str會作為引數原封不動的傳遞給表示式str.toUpperCase(),這樣就可以使用String::toUpperCase改寫。

(2) filter 的詳解:filter對原始 Stream 進行某項測試,通過測試的元素被留下來生成一個新 Stream。

例2:過濾陣列中的奇數,留下偶數

    @Test
    public void filterTest(){
        //留下偶數
        Integer[] nums = {1,2,3,4,5,6};
        Integer[] evens = Stream.of(nums).
                filter(n -> n%2 == 0).
                toArray(Integer[]::new);
        for (Integer even : evens) {
            System.out.println(even);
        }
    }

例3:將檔案中的單詞挑出,轉為小寫,排序並保證不重複

    @Test
    public void testDistinct() {
        try {
            //
            BufferedReader br = new BufferedReader(new FileReader("src/main/resource/test.txt"));
            //獲取不重複的單詞集合
            List<String> wordList = br.lines().
                    flatMap(line -> Stream.of(line.split(" "))).
                    filter(word -> word.length() > 0).
                    map(String::toLowerCase).
                    distinct().
                    sorted().
                    collect(Collectors.toList());
            System.out.println(wordList);
            br.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

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

例4:字串拼接,求最小值、最大值、過濾字串拼接

    @Test
    public void reduce(){
        //字串連線
        String concat1 = Stream.of("A","B","C","D").reduce("", (x,y) -> x.concat(y));
        String concat2= Stream.of("A","B","C","D").reduce("", String::concat);
        System.out.println(concat1 + concat2);

        //求最小值
        double minValue1 = Stream.of(-1.5,1.0,-3.0,-2.0).reduce(Double.MAX_VALUE, (x,y) -> Double.min(x, y));
        double minValue2 = Stream.of(-1.5,1.0,-3.0,-2.0).reduce(Double.MAX_VALUE, Double::min);
        System.out.println(minValue1);
        System.out.println(minValue2);

        //求最和
        int sumValue1 = Stream.of(1,2,3,4).reduce(0, (x,y) -> x + y);
        //原因:Integer類中的sum()方法完全可以替代兩個數求和,引數型別、個數、返回值相同
        int sumValue2 = Stream.of(1,2,3,4).reduce(0, Integer::sum);
        System.out.println(sumValue1);
        System.out.println(sumValue2);

        //過濾字串並拼接
        String concat = Stream.of("a","D","c","B","C","A").
                filter(s -> s.compareTo("Z") > 0).
                reduce("", String::concat);
        System.out.println(concat);
    }

(4) limit/skip的詳解:limit 返回 Stream 的前面 n 個元素;skip 則是扔掉前 n 個元素。

例5:留下陣列中的偶數,並扔掉前4 個元素。

    @Test
    public void testLimitAndSkip() {
        List<Integer> list = new ArrayList();
        for (int i = 1; i <= 20; i++) {
            list.add(i);
        }
        //limit()返回前n個元素,skip則是扔掉前n個元素
        List<Integer> list1 = list.stream().
                filter(x -> x % 2 == 0).limit(10).skip(3).collect(Collectors.toList());
        System.out.println(list1);
    }

(5) sorted的詳解:對 Stream 的排序通過 sorted 進行,它比陣列的排序更強之處在於你可以首先對 Stream 進行各類 map、filter、limit、skip 甚至 distinct 來減少元素數量後,再排序,這能幫助程式明顯縮短執行時間。

    @Test
    public void testLimitAndSkip1() {
        Integer[] arr = new Integer[]{2,5,3,4,7,8,5,12,13,16,18,19,20};
        List<Integer> list = Arrays.asList(arr);
        //limit()返回前n個元素,skip則是扔掉前n個元素
        List<Integer> list1 = list.stream().
                sorted().       //先排序,從I小到大輸出
                filter(x -> x % 2 == 0).
                limit(10).
                skip(3).
                collect(Collectors.toList());
        System.out.println(list1);
    }

(6) max/min/distinct的詳解:保證Stream流集合中元素不重複,找出最大值、最小值等

    @Test
    public void testMax() {
        Integer[] arr = new Integer[]{2,5,3,4,7,8,5,12,13,16,18,19,20};
        List<Integer> list = Arrays.asList(arr);
        //求最大值
        Integer maxValue = list.stream().
                max(Integer::compare).get();
        System.out.println(maxValue);

        //求最小值
        Integer minValue = list.stream().
                min(Integer::compare).get();
        System.out.println(minValue);
    }

還有forEach(),peek(),allMatch(),anyMatch(),noMatch()方法,自行研究和舉例,這裡不在一一贅述!

三、Stream總結:

   (1) Stream不是資料結構;

   (2) 它沒有內部儲存,它只是用操作管道從 source(資料結構、陣列、generator function、IO channel)抓取資料;

   (3) 它也絕不修改自己所封裝的底層資料結構的資料。例如 Stream 的 filter 操作會產生一個不包含被過濾元素的新 Stream,而不          是從 source 刪除那些元素;

   (4) 所有 Stream 的操作必須以 lambda 表示式為引數;

   (5) 不支援索引訪問;使用forEach()遍歷陣列,不可使用break,continue等關鍵字跳出迴圈;

   (6) 你可以請求第一個元素findFirst(),但無法請求第二個,第三個,或最後一個。不過請參閱下一項。

   (7) 很容易生成陣列或者 List。

簡單的講解了一下Java8新特性之Stream語法,希望大家看過後有所收穫,共同進步......

歡迎續學:Java8新特性之Stream學習二