Java8 Stream API 之 IntStream 用法全解

  目錄

1、創建int流

2、filter / map / flatMap /peek

3、mapToObj / mapToLong / mapToDouble / asLongStream / asDoubleStream

4、forEach / forEachOrdered

5、 reduce / collect

6、distinct / sorted / limit / skip

7、sum / min / max / count / average / summaryStatistics

8、anyMatch / allMatch / noneMatch

9、findFirst / findAny

10、sequential / parallel

11、iterator / boxed / toArray

12、close / onClose


       Java8支持的流處理的元素類型只有4種,double、int,long和reference類型,可參考AbstractPipeline的類繼承關係,如下:

AbstractPipeline有4個子類,分別表示流處理元素爲double、int,long和reference類型的管道;這4個子類都是抽象類,每個子類下都有3個靜態內部類的實現類,Head、StatefulOp、StatelessOp,其中Head用於創建一個全新的流,StatefulOp表示有狀態的一類操作,StatelessOp表示無狀態的一類操作,這裏的有狀態是指前面流元素的處理會直接影響後面流元素的處理,多線程並行處理下每次運行的結果都不相同。前面3個AbstractPipeline子類的用法和實現是基本一致的,本篇博客以IntPipeline爲例來說明其實現接口IntStream 的使用細節,下篇博客詳細說明其實現細節,該類的類繼承關係如下:

其中IntStream接口定義了int流支持的所有操作。 

1、創建int流

      創建int流都是由IntStream接口類中的靜態方法完成的,具體如下:

  • of / builder: 可指定int流中包含的具體單個元素
  • range / rangeClosed : 將指定範圍內的元素都添加到int流中,前者不包含最後一個元素,後者包含
  • generate / iterate :  指定生成int流中int元素的生成函數,前者的生成函數沒有入參,後者會將前一次調用結果作爲下一次調用生成函數的入參
  • concat :將兩個int流合併成一個

各方法的使用細節可參考如下測試用例:

 @Test
    public void test() throws Exception {
        //包含指定的元素
//        IntStream intStream=IntStream.of(1);
        //返回的int流中的元素是已經排序好的
        IntStream intStream=IntStream.of(1,3,2,5,4,6);
        print("of",intStream);

        //從11到16,不包含16
        intStream=IntStream.range(11,16);
        //從11到16,包含16
//        intStream=IntStream.rangeClosed(11,16);
        print("range",intStream);

        //包含指定的元素,add方法底層也是調用accept方法,然後返回this
        //返回的int流中的元素順序與添加順序一致
        intStream=IntStream.builder().add(23).add(22).add(21).build();
        print("builder", intStream);

        //指定一個int生成函數
        //返回的int流中的元素不排序
        intStream=IntStream.generate(()->{
            Random random=new Random();
            return random.nextInt(100);
        }).limit(6);
        print("generate", intStream);

        //指定一個int生成函數,前一次執行函數的結果會作爲下一次調用函數的入參
        //第一個參數seed就是第一次調用生成函數的入參
        //返回的int流中的元素不排序
        intStream=IntStream.iterate(1,x->{
           int a=2*x;
           if(a>16){
               return a-20;
           }else{
               return a;
           }
        }).limit(6);
        print("iterate", intStream);
    }

    @Test
    public void test2() throws Exception {
        IntStream streamA=IntStream.range(11,15);
        IntStream streamB=IntStream.range(6,10);
        //將兩個IntStream 合併起來
        //返回的int流的元素順序與添加的流的元素順序一致,不排序
        IntStream streamC=IntStream.concat(streamA,streamB);
        print("concat", streamC);
    }

    private void print(String start, IntStream intStream){
        System.out.println("print for->"+start);
        intStream.forEach(x->{
            System.out.println(x);
        });
    }

 上述方法的底層實現最終都是調用StreamSupport.intStream(Spliterator.OfInt spliterator, boolean parallel)方法,如下圖:

2、filter / map / flatMap /peek

      filter方法會將filter函數返回false的元素從流中去除,只保留filter函數返回true的元素;map方法用於對流中的所有元素執行某個修改動作;peek方法通常用於打印流中的元素,peek函數無返回值;flatMap方法同map方法,區別在於flatMap函數的返回值是一個IntStream 而非int值,可以在返回的IntStream中包含多個元素,flatMap方法最終返回的IntStream是將每次調用flatMap函數返回的IntStream 合併後的結果。其測試用例如下:

  @Test
    public void test3() throws Exception {
        IntStream intStream=IntStream.rangeClosed(1, 10);
        //會保留過濾函數返回true的元素,此處是保留偶數
        intStream=intStream.filter(x->{
           return x%2==0;
        }).peek(x->{ //peek方法指定的函數,以流中的元素爲入參,無返回值,即不會修改元素本身
            System.out.println("filter->"+x);
        });
        //對流中的所有元素執行某個修改動作,此處是將所有值加1
        intStream=intStream.map(x->{
            return x+1;
        }).peek(x->{
            System.out.println("map->"+x);
        });

        //flatMap同map,區別在於flatMap指定的函數其返回值是一個IntStream,而非一個int值,最終flatMap返回的
        //IntStream是將每次調用flatMap返回的子IntStream合併後的結果
        intStream=intStream.flatMap(x->{
            //返回IntStream時可以返回多個元素
            return IntStream.of(x+3,x+2,x+1);
        }).peek(x->{
            System.out.println("flatMap->"+x);
        });

        print("after flatMap", intStream);
    }

其輸出如下:

print for->after flatMap
filter->2
map->3
flatMap->6
6
flatMap->5
5
flatMap->4
4
filter->4
map->5
flatMap->8
8
flatMap->7
7
flatMap->6
6
filter->6
map->7
flatMap->10
10
flatMap->9
9
flatMap->8
8
filter->8
map->9
flatMap->12
12
flatMap->11
11
flatMap->10
10
filter->10
map->11
flatMap->14
14
flatMap->13
13
flatMap->12
12

 其中因爲flatMap函數返回的IntStream中包含了3個元素,所以peek方法執行了3次,每次都打印三個flatMap。從上述輸出可知,只有開始執行forEach方法後才真正執行流處理動作,且原始的intStream只遍歷一遍,每次遍歷一個元素就按照代碼聲明的順序依次執行各個處理動作。

3、mapToObj / mapToLong / mapToDouble / asLongStream / asDoubleStream

      這幾個方法都是將int流轉換成其他類型的流,mapTo的三個方法可以指定具體的轉換函數,as的兩個方法不能指定,遵循標準的int到指定類型的轉換規則,其實現如下:

 @Test
    public void test4() throws Exception {
        IntStream intStream=IntStream.rangeClosed(1, 3);
        intStream.mapToObj(x->{
            return new Age(x);
        }).forEach(x->{
            System.out.println("mapToObj->"+x);
        });

        //執行完mapToObj後intStream本身已經關閉了不能繼續操作,只能操作其返回的新流
        //此處要繼續操作就必須重新初始化一個新的IntStream
        intStream=IntStream.rangeClosed(1, 3);
        intStream.mapToLong(x->{
            return x+1;
        }).forEach(x->{
            System.out.println("mapToLong->"+x);
        });

        intStream=IntStream.rangeClosed(1, 3);
        intStream.mapToDouble(x->{
            return x+2;
        }).forEach(x->{
            System.out.println("mapToDouble->"+x);
        });

        //同上面的mapToLong,區別在於不能指定轉換函數,而是採用標準的int到long類型的轉換方法
        intStream=IntStream.rangeClosed(1, 3);
        intStream.asLongStream().forEach(x->{
            System.out.println("asLongStream->"+x);
        });

        intStream=IntStream.rangeClosed(1, 3);
        intStream.asDoubleStream().forEach(x->{
            System.out.println("asDoubleStream->"+x);
        });
    }

其輸出如下:

4、forEach / forEachOrdered

      這兩個方法都是用來遍歷流中的元素,跟peek方法不同的是該這兩個方法的返回值是void,執行此類返回值爲void的方法會觸發實際的流處理動作。forEachOrdered同forEach的區別在於並行流處理下,forEachOrdered會保證實際的處理順序與流中元素的順序一致,而forEach方法無法保證,默認的串行流處理下,兩者無區別,都能保證處理順序與流中元素順序一致,測試用例如下:

@Test
    public void test6() throws Exception {
        IntStream intStream=IntStream.of(6,1,3,2,5,4).parallel();
        intStream.forEach(x->{
            System.out.println("forEach->"+x);
        });

        //forEachOrdered同forEach,區別在於並行流處理下,forEachOrdered會保證實際的處理順序與流中元素的順序一致
        //而forEach方法無法保證,默認的串行流處理下,兩者無區別,都能保證處理順序與流中元素順序一致
        intStream=IntStream.of(6,1,3,2,5,4).parallel();
        intStream.forEachOrdered(x->{
            System.out.println("forEachOrdered->"+x);
        });
    }

  其輸出如下:

5、 reduce / collect

       reduce方法用於執行類似於累加的操作,上一次調用處理函數的結果會作爲入參下一次調用的入參;collect方法的效果跟forEach類似,注意其返回值是調用supplier函數的返回值,該函數在整個流處理過程中只調用一次,參考如下測試用例:

@Test
    public void test7() throws Exception {
        IntStream intStream=IntStream.of(6,1,3,2,5,4);
        OptionalInt optionalInt=intStream.reduce((x, y)->{
            System.out.println("x->"+x+",y->"+y);
            return x+y;
        });
        System.out.println("result->"+optionalInt.getAsInt());

        System.out.println("");

        intStream=IntStream.of(6,1,3,2,5,4);
        //同第一個reduce方法,區別在於可以指定起始的left,第一個reduce方法使用第一個元素作爲起始的left
        int result=intStream.reduce(2,(x, y)->{
            System.out.println("x->"+x+",y->"+y);
            return x+y;
        });
        System.out.println("result->"+result+"\n");

        intStream=IntStream.of(6,1,3,2,5,4);
        //同forEach方法,首先調用supplier函數生成一個值,將該值作爲accumulator函數的第一個參數,accumulator函數的第二個
        //參數就是流中的元素,注意第三個參數combiner無意義,可置爲null
        result=intStream.collect(()->{
            Random random=new Random();
            return random.nextInt(10);
        },(x,y)->{
            System.out.println("ObjIntConsumer x->"+x+",y->"+y);
        },null);
        //返回值是supplier函數生成的值
        System.out.println("collect result->"+result+"\n");

    }

其輸出如下:

6、distinct / sorted / limit / skip

      distinct用於對流中的元素去重,sorted用於對流中的元素升序排序,limit用於限制流中元素的個數,多餘的元素會被丟棄,skip用於跳過前面指定N個元素,參考如下測試用例:

  @Test
    public void test8() throws Exception {
        IntStream intStream=IntStream.of(6,1,1,2,5,2,3,3,4,8,6,11,10,9);
        intStream.distinct() //對流中的元素去重
                 .sorted()  //將流中的元素排序,默認升序
                 .skip(3) //跳過前3個元素,此處是跳過1,2,3三個元素
                 .limit(6) //限制流中元素的最大個數
                 .forEach(x->{
                     System.out.println(x);
                 });

    }

 其輸出如下:

 

7、sum / min / max / count / average / summaryStatistics

     前面5個方法分別是獲取流中元素的總和,最小值,最大值,個數和平均值,最後一個summaryStatistics方法是一次調用獲取上述屬性。測試用例如下:

@Test
    public void test9() throws Exception {
        IntStream intStream=IntStream.of(6,1,1,2,5,2,3,4);
        //取流中元素的最大值
        OptionalInt max=intStream.max();
        System.out.println("max->"+max.getAsInt());

        //同其他沒有流的方法,max操作會中斷流,再對該流執行相關流處理方法會報錯synchronizedTest.StreamTest
        intStream=IntStream.of(6,1,1,2,5,2,3,4);
        //取流中元素的最小值
        OptionalInt min=intStream.min();
        System.out.println("max->"+max.getAsInt());

        intStream=IntStream.of(6,1,1,2,5,2,3,4);
        //取流中元素的平均值
        OptionalDouble average=intStream.average();
        System.out.println("average->"+average.getAsDouble());

        intStream=IntStream.of(6,1,1,2,5,2,3,4);
        //取流中元素的個數
        long count=intStream.count();
        System.out.println("count->"+count);

        intStream=IntStream.of(6,1,1,2,5,2,3,4);
        //取流中元素的總和
        int sum=intStream.sum();
        System.out.println("sum->"+sum);

        intStream=IntStream.of(6,1,1,2,5,2,3,4);
        //取流中元素的統計情形,即一次返回min,max,count等屬性
        IntSummaryStatistics summaryStatistics=intStream.summaryStatistics();
        System.out.println(summaryStatistics.toString());
    }

 其輸出如下:

 

8、anyMatch / allMatch / noneMatch

     有任何一個元素匹配,anyMatch返回true;所有元素都匹配時,allMatch返回true;沒有一個元素匹配時,noneMatch返回true,參考如下測試用例:

@Test
    public void test10() throws Exception {
        IntStream intStream = IntStream.of(6, 1, 1, 2, 5, 2, 3, 4);
        //有任何一個匹配,返回true
        boolean anyMatch=intStream.anyMatch(x->{
            return x%2==0;
        });
        System.out.println("anyMatch->"+anyMatch);

        intStream = IntStream.of(6, 1, 1, 2, 5, 2, 3, 4);
        //所有的都匹配,返回true
        boolean allMatch=intStream.allMatch(x->{
            return x%2==0;
        });
        System.out.println("allMatch->"+allMatch);

        intStream = IntStream.of(6, 1, 1, 2, 5, 2, 3, 4);
        //所有的都不匹配,返回true
        boolean noneMatch=intStream.noneMatch(x->{
            return x%2==0;
        });
        System.out.println("noneMatch->"+noneMatch);
    }

 輸出如下:

 

9、findFirst / findAny

      findFirst返回流中第一個元素,findAny返回流中的任意一個元素,參考如下用例:

@Test
    public void test11() throws Exception {
        IntStream intStream = IntStream.of(6, 1, 1, 2, 5, 2, 3, 4);
        //返回第一個元素
        OptionalInt result=intStream.findFirst();
        System.out.println("findFirst->"+result.getAsInt());

        for(int i=0;i<6;i++) {
            intStream = IntStream.of(6, 1, 1, 2, 5, 2, 3, 4);
            //返回任意一個元素
            result = intStream.findAny();
            System.out.println("findAny->" + result.getAsInt());
        }
    }

10、sequential / parallel

       sequential 返回的流是串行處理,parallel返回的流是並行處理,參考如下測試用例:

@Test
    public void test12() throws Exception {
        IntStream intStream = IntStream.of(6, 1, 1, 2, 5, 2, 3, 4);
        long start=System.currentTimeMillis();
        //並行處理
        intStream.parallel().forEach(x->{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+":"+x);
        });
        System.out.println("parallel time->"+(System.currentTimeMillis()-start));

        intStream = IntStream.of(6, 1, 1, 2, 5, 2, 3, 4);
        start=System.currentTimeMillis();
        //默認都是串行處理
        intStream.sequential().forEach(x->{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+":"+x);
        });
        System.out.println("sequential time->"+(System.currentTimeMillis()-start));
    }

 其輸出如下:

 

從輸出可知parallel返回的流起了4個線程來處理,主線程和3個commonPool線程,這4個線程並不是嚴格的從一開始就並行執行,因爲其執行總耗時是2061ms,而不是1s左右;串行處理是各個元素的處理耗時累加起來,8個元素,剛好是8s。 

11、iterator / boxed / toArray

      iterator 返回一個元素遍歷器實現,boxed 返回一個流中元素的包裝類的流,可用mapToObj實現相同的功能,toArray將流中的元素作爲一個數組返回,參考如下測試用例:

@Test
    public void test13() throws Exception {
        IntStream intStream = IntStream.of(6, 1, 1, 2, 5, 2, 3, 4);

        //返回一個包裝類的流,效果相當於下面的mapToObj方法
//        Stream<Integer> integerStream=intStream.boxed();
        Stream<Integer> integerStream=intStream.mapToObj(x->{
            return new Integer(x);
        });
        integerStream.forEach(x->{
            System.out.println("boxed->"+x);
        });

        //返回一個元素遍歷器,PrimitiveIterator是繼承自Iterator
        intStream = IntStream.of(6, 1, 1, 2, 5, 2, 3, 4);
        PrimitiveIterator.OfInt iterator=intStream.iterator();
        while (iterator.hasNext()){
            System.out.println("iterator->"+iterator.next());
        }

        //將流中的元素轉換成一個數組
        intStream = IntStream.of(6, 1, 1, 2, 5, 2, 3, 4);
        int[] results=intStream.toArray();
        System.out.println("toArray->"+Arrays.toString(results));
    }

其輸出如下:

 

12、close / onClose

       close方法會關閉流,並觸發所有的onClose方法的執行;onClose方法用於註冊一個回調函數,該方法返回一個新的流,可以連續調用註冊多個回調函數,close觸發時會按照註冊的順序依次執行。參考如下測試用例:

@Test
    public void test14() throws Exception {
        IntStream intStream = IntStream.of(6, 1, 1, 2, 5, 2, 3, 4);

        //onClose方法的返回值是一個新的流,可以連續調用onClose,註冊多個回調方法
        intStream.onClose(()->{
            System.out.println("intStream isClosed one ");
        }).onClose(()->{
            System.out.println("intStream isClosed two");
        }).onClose(()->{
            System.out.println("intStream isClosed three");
        });

        //觸發onClose方法註冊的多個回調方法的執行,並關閉流
        intStream.close();
        //流已關閉,不能執行流處理動作,forEach執行完成也會關閉流但是不會觸發onClose方法的執行
//        intStream.forEach(x->{
//            System.out.println(x);
//        });
        System.out.println("main end");
    }

其輸出如下:

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章