java8學習:使用流(1)

內容來自《 java8實戰 》,本篇文章內容均爲非盈利,旨爲方便自己查詢、總結備份、開源分享。如有侵權請告知,馬上刪除。
書籍購買地址:java8實戰

  • 篩選和切片

    • filter方法
    //取出是素菜的菜單
    menu.stream()
          .filter(Dish::isVegetarian)   //代表是否是素食,這裏條件並不一定是方法調用,只要的你條件能夠返回boolean就可以
          .collect(Collectors.toList());
    • limit方法
    //取出菜單的前三個
    menu.stream().limit(3)              //跟sql中limit一樣,不過這個是短路limit,也就是說,它取出了三個後就不再遍歷後面的元素了,直接返回
                 .collect(Collectors.toList());
    • distinct方法:去重
    menu.stream()
        .distinct()  //去重操作,根據元素的hashCode和equals方法判斷是否相等
        .collect(Collectors.toList());
    • skip方法:跳過指定數量的元素
    //取菜單中第三個到第五個
    menu.stream()
            .skip(2)   //跳過開頭兩個元素
            .limit(3)  //然後截取第三個到第五個
            .forEach(System.out::println);
    //如上可以看出skip和limit是互補的,因爲limit沒有這樣的形式:limit(start,end)
  • 映射

    • 提到這個概念,就是把你stream裏的每個元素都應用到自己指定的表達式之上然後返回,就比如
    List<String> list = Arrays.asList("1","2","3");
    list.stream()
            .映射方法(str -> str + "dada")
            .forEach(System.out::println);
    //以上爲僞代碼
    //說明的意思就是list中的每個元素:1,2,3都會應用到表達式:str + "dada"
    //並返回表達式的值
    //所以結果就是1dada,2dada,3dada
    • map方法
    //這裏提到的map方法,完全可以替換上面的僞代碼:映射方法,上面代碼是傳入A類型返回A類型的演示
    //也就是傳入字符串1,2,3返回的也是字符串
    //map當然可以傳入A類型返回B類型,比如
    List<String> list = Arrays.asList("1","2","3");
    list.stream()
            .map(Integer::parseInt)
            .forEach(System.out::println);
    //上面就是傳入String,返回的是int
    //當然也可以是返回字符串的長度
    • 考慮一個場景,把n個單詞的裏面的字母去重然後輸出

      • 就比如["abc","bce"],裏面的字母就是["a","b","c","b","c","e"],去重後是:["a","b","c","e"],現在來實現它
      List<String> list = Arrays.asList("abc","bce");
      List<String[]> collect = list.stream()
              .map(str -> str.split(""))
              .distinct()
              .collect(Collectors.toList());
      collect.forEach(System.out::println);
      • 上面的感覺是對的,拆分,然後去重,但是一點需要注意,當lambda把第一個元素"abc"進行映射操作的時候,split方法返回的是一個String數組對象,這也就能解釋爲什麼List的泛型是String數組了,數組跟數組distinct那肯定是去重不成功的。
      • 流的過程是這樣的["abc","bce"] -> [{"a","b","c"},{"b","c","e"}],然後拿{"a","b","c"}和{"b","c","e"}去重肯定行不通
      • 現在遇到的問題就是:我們希望把abc和bce切分然後合併成一個流,然後進行去重,我們接着實驗
      • Array::stream

        • Array::stream可以接受一個數組併產生一個流,如下
        String[] str = {"abc","bce"};
        Stream<String> stream = Arrays.stream(str);
        stream.forEach(System.out::println);
        //abc
        //bce
        • 我們在上面的split方法返回的就是數組,我們嘗試將split返回的數組映射到此方法上,看看能不能合併成一條流
        List<String> list = Arrays.asList("abc","bce");
        List<Stream<String>> collect = list.stream()
                .map(str -> str.split(""))
                .map(Arrays::stream)
                .distinct()
                .collect(Collectors.toList());
        //java.util.stream.ReferencePipeline$Head@2aae9190
        //java.util.stream.ReferencePipeline$Head@2f333739
        • 但是並不盡如人意,因爲他返回的泛型是Stream,也就是說,Array::stream方法只是把一個數組轉換爲一個流,流中的元素是String所以這個流就是這樣的Stream,這種情況類似上面遇到的返回的是兩個數組,現在返回的List中裝入的並不是String,而是兩條流,流跟流做去重也是行不通的
      • 解決方案

        • 現在遇到的問題就是:他們都是將一個list元素單獨轉化爲一條流或者一個數組,我們需要的是轉換爲流或者數組之後,再將這些返回的數組或者流接爲一個流或者數組,就像接水管一樣,讓他們連起來
        • 嘗試方法:flatmap
        List<String> list = Arrays.asList("abc","bce");
        List<String> collect = list.stream()
                .map(str -> str.split(""))
                .flatMap(Arrays::stream)
                .distinct()
                .collect(Collectors.toList());
        collect.forEach(System.out::println);
        //a b c e
        • 如上的代碼終於是返回了一個LIst,所以結果也是我們期待的達到了去重的效果,那麼它的流的處理過程是怎麼樣的呢?如下

          • 傳入"abc"->切分爲["a","b","c"]
          • --->flatMap合併["a","b","c","b","c","e"]->去重->ok
          • 傳入"bce"->切分爲["b","c","e"]
          • 上面能看懂嗎...懶得畫圖了,第一行和第三行是flatMap前面的split過程,然後是第二行存入flatMap進行合併流
        • flatMap也就是把n個流合併爲一個流
      • 小實戰

        • 要求兩個list:["123"],["456"],然後組成元組:(1,4),(1,5),(1,6),(2,4),(2,5)...
        List<String> first = Arrays.asList("123");
        List<String> second = Arrays.asList("456");
        first.stream()
                .map(str -> str.split(""))
                .forEach(strs -> {
                    Arrays.stream(strs).forEach(f -> {
                        second.stream()
                                .map(sec -> sec.split(""))
                                .forEach(secs -> Arrays.stream(secs).forEach(s -> System.out.println("("+f+","+s+")")));
                    });
                });
        //上面代碼是直接輸出的,那麼下面的就是將元組組合成一個LIst返回了
        List<String> first = Arrays.asList("123");
        List<String> second = Arrays.asList("456");
        List<String> collect = first.stream()
                .map(str -> str.split(""))
                .flatMap(strs ->              //將strs後的表達式返回的stream都合併爲一個
                        Arrays.stream(strs).flatMap(f ->
                                second.stream()
                                        .map(sec -> sec.split(""))
                                        .flatMap(secs -> Arrays.stream(secs).map(s -> "(" + f + "," + s + ")")))
                ).collect(Collectors.toList());
        collect.forEach(System.out::println);
        //耐心看.....

查找和匹配

  • anyMatch:至少匹配一個
List<String> list = Arrays.asList("1","2","3","4");
boolean b = list.stream().anyMatch(s -> s.equals("1")); //true
  • allMatch:全部匹配
List<String> list = Arrays.asList("1","2","3","4");
boolean b = list.stream().allMatch(s -> Integer.parseInt(s) < 5); //true
  • noneMatch:全部不匹配
List<String> list = Arrays.asList("1","2","3","4");
boolean b = list.stream().noneMatch(s -> Integer.parseInt(s) > 5); //true
  • 如上的方法都具有短路效果,意思就是主要遇到一個不匹配條件的元素就立刻返回true或者false
  • findAny:找任意一個
  List<String> list = Arrays.asList("1","2","3","4");
  Optional<String> any = list.stream().findAny();
  System.out.println(any.get()); //雖然書上說是任意一個,但是我一直返回的是1
  //自己測試的將元素添加到十個,stream依舊是1,但是使用並行流parallelStream,將隨機返回,但是依然是區間比較小
  • Option以後的帖子會說到的,你可以看一下我相關的帖子,只要記住Option是一個值的容器,因爲findAny找的可能是個空列表,所以他可能會返回null,然後將返回的這個值包裝在Option中,然後調用get方法就會出現返回的值,如果Option中沒有值還get那麼就會報錯,現在只要知道這點就可以了
  • findFirst:找到第一個
List<String> list = Arrays.asList("1","2","3","4");
Optional<String> any = list.stream().findFirst();
System.out.println(any.get()); //1

歸約reduce

  • 上面的映射和這詞提到的歸約可以一起使用,類似hadoop中的mr模型
  • 元素求和
int[] is = {1,2,3,4,5,6,7,8,9};
Arrays.stream(is).reduce(0,Integer::sum);//45  
//以0爲起始值,所以如果數組內沒有元素,也不至於null,然後依次相加
//0+1=1
//1+2=3
//3+3=6
//6+4=10 ....
int[] is = {1,2,3,4,5,6,7,8,9};
OptionalInt reduce = Arrays.stream(is).reduce(Integer::sum);
System.out.println("reduce = " + reduce.getAsInt());//45
//OptionalInt的出現就是因爲它沒有初始值進行累加,所以如果數組爲空,那麼將返回null
//OptionalInt使用方法和Optional一樣,只是方法名變了一下,目前只知道這些就好了
  • 元素最大值和最小值
//max
int[] is = {1,2,3,4,5,6,7,8,9};
OptionalInt reduce = Arrays.stream(is).reduce(Integer::max);
System.out.println("reduce = " + reduce.getAsInt()); //9
//求最小改爲min方法即可,也可以自己實現
OptionalInt reduce = Arrays.stream(is).reduce((a,b) -> a > b ? a : b );
  • 歸約方法的優勢和並行化

    • 求和方法:定義一個int變量,然後迭代去加。相比之下reduce將其轉換爲了內部迭代。而且迭代要去求和並且更新我們的一個int共享變量,這對於並行化來說並不容易實現,如果加入了同步,可能線程切換的開銷就已經抵消了並行帶來的性能提升。(可變的變量累計器對於並行來說並不好),如上的代碼爲了實現並行只需要把stream方法改爲parallelStream()即可
    int[] is = {1,2,3,4,5,6,7,8,9};
    int sum = 0;    //int共享變量
    //這只是單線程的,如果是多線程的話,爲了保證sum的正確肯定要sync。
    //所以這就是說的共享變量並不適合於並行化
    for (int i : is) {
        sum += i;     //更新共享變量
    }
  • 流操作的有狀態和無狀態

    • 如果你購買了書可以先去看看書的定義,下面是我自己的理解
    int[] is = {1,2,3,4,5,6,7,8,9};
    Arrays.stream(is)
            .filter(i -> i > 3)  //無狀態
            .map(i -> i + 1)     //無狀態
            .distinct()          //有狀態
            .max();              //有狀態
    • 對於上面來說,filter和map只是接收一個元素然後過濾映射一個元素,這個元素處理完就交給下面的方法處理了,自己並不保留這個元素,這樣的叫做無狀態
    • 那distinct和max來說,他不能接收一個處理一個然後再交給下面的方法處理,因爲它需要拿到整個元素來去重和去最大值,如果拿到部分他肯定是不能做這些操作的,元素就暫時的保留在了方法中,所以這樣的叫做有狀態
接下來的東西下一篇講
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章