java8 Streams API 詳解

什么是stream

Stream作為java8 的一大亮點(diǎn),與InputStream和OutPutStream是完全不同的概念。也不同于stax對(duì)xml解析的Stream,也不是Amazon kinesis對(duì)大數(shù)據(jù)實(shí)時(shí)處理的Stream。java8 中的Stream是對(duì)集合(Collection)對(duì)象功能的增強(qiáng)。專注于對(duì)集合對(duì)象進(jìn)行非常便利、高效的聚合操作。同時(shí)它提供串行和并行兩種模式,并發(fā)模式能夠充分利用多核處理器的優(yōu)勢(shì)。使用frok\join 并行方式拆分和加速任務(wù)。

什么是聚合操作

java代碼經(jīng)常不得不依賴于關(guān)系型數(shù)據(jù)庫(kù)的聚合操作來(lái)完成例如:

  • 客戶每月平均消費(fèi)金額
  • 最昂貴的在售商品
  • 本周完成的有效訂單(排除無(wú)效的)
  • 取十個(gè)數(shù)據(jù)樣本作為首頁(yè)推薦

這類操作。

在java7 中如果要發(fā)現(xiàn)type為grocery的所有交易,然后返回以交易值降序排序好的交易ID集合,需要這樣寫(xiě):

List<Transaction> groceryTransactions = new Arraylist<>();
for(Transaction t: transactions)  {
     if(t.getType() == Transaction.GROCERY)  {
     groceryTransactions.add(t);
     }
}
Collections.sort(groceryTransactions, new Comparator()  {
     public int compare(Transaction t1, Transaction t2)  {
     return t2.getValue().compareTo(t1.getValue());
     }
});
List<Integer> transactionIds = new ArrayList<>();
for(Transaction t: groceryTransactions){
     transactionsIds.add(t.getId());
}

而在java8中使用Stream,代碼更加簡(jiǎn)潔易讀,而且使用并發(fā)模式,程序執(zhí)行速度更快。

List<Integer> transactionsIds = transactions.parallelStream().
  filter(t - > t.getType() == Tranusaction.GROCERY).
  sorted(comparing(Transaction :: getBalue).reversed()).
  map(Transaction::getId).
collect(toList());

Stream 什么是流

Stream不是集合元素,它不是數(shù)據(jù)結(jié)構(gòu)并不保存數(shù)據(jù),他是有關(guān)算法和計(jì)算的,更像一個(gè)高級(jí)版本的Iterator.原始版本的Iterator,用戶只能顯式地一個(gè)一個(gè)遍歷元素并對(duì)其執(zhí)行某些操作;高級(jí)版本的Stream,用戶只要給出需要對(duì)其包含的元素執(zhí)行什么操作。比如“過(guò)濾掉長(zhǎng)度大于10的字符串”,“獲取每個(gè)字符串的首字母”等,Stream會(huì)隱式的在內(nèi)部進(jìn)行遍歷,做出相應(yīng)的數(shù)據(jù)轉(zhuǎn)換。

Stream就如果一個(gè)迭代器(Iterator),單向,不可往復(fù),只能遍歷一次,遍歷一次后即用盡了,就好比流水從面前流過(guò),一區(qū)不復(fù)返

而和迭代器又不同的是,Stream可以并行化操作,Stream的并行操作依賴于jdk1.7引入的Fork/Join框架來(lái)拆分任務(wù)和加速處理過(guò)程。

流的構(gòu)成

當(dāng)我們使用一個(gè)流的時(shí)候,通常包括三個(gè)基本步驟:

  • 獲取一個(gè)數(shù)據(jù)源(Source)
  • 數(shù)據(jù)轉(zhuǎn)換
  • 執(zhí)行操作獲取想要的結(jié)果

每次轉(zhuǎn)換原有Stream對(duì)象不改變,返回一個(gè)新的Stream對(duì)象,這就允許對(duì)其操作可以像鏈條一樣排列,變成一個(gè)管道。如下圖所示。


圖1:流管道的構(gòu)成

有多種方式生成Stream Source:

從Collection和數(shù)組

  • Collection.stream()
  • Collection.parallelStream()
  • Arrays.stream(T array) or Stream.of()

從BufferedReader

  • java.io.BufferedReader.lines()

靜態(tài)工廠

  • java.util.stream.IntStream.range()
  • java.nio.file.Files.walk()

自己構(gòu)建

  • java.util.Spliterator

其他

  • Random.ints()
  • BitSet.stream()
  • Pattern.splitAsStream(java.lang.CharSequence)
  • JarFile.stream()

流的操作類型分為兩種

  • Intermediate
  • Terminal

Intermediate:一個(gè)流后面可以跟隨0個(gè)或多個(gè)intermediate操作。其主要目的是打開(kāi)流,做出某種程度的數(shù)據(jù)映射/過(guò)濾,然后返回一個(gè)新的流,交給下一個(gè)操作使用。這類操作都是惰性化的(lazy),也就是說(shuō),僅僅調(diào)用這類方法,并沒(méi)有真正開(kāi)始流的遍歷。

Terminal:一個(gè)流只能有一個(gè)terminal操作,當(dāng)這個(gè)操作執(zhí)行后,流就被使用“光”了。無(wú)法再被操作。所以這必定是流的最后一個(gè)操作。Terminal操作的執(zhí)行,才會(huì)真正開(kāi)始流的遍歷,并且會(huì)生成一個(gè)結(jié)果,或者一個(gè)side effect。

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

  • 對(duì)于一個(gè) intermediate 操作,如果它接受的是一個(gè)無(wú)限大(infinite/unbounded)的 Stream,但返回一個(gè)有限的新 Stream。
  • 對(duì)于一個(gè) terminal 操作,如果它接受的是一個(gè)無(wú)限大的 Stream,但能在有限的時(shí)間計(jì)算出結(jié)果。

當(dāng)操作一個(gè)無(wú)限大的 Stream,而又希望在有限時(shí)間內(nèi)完成操作,則在管道內(nèi)擁有一個(gè) short-circuiting 操作是必要非充分條件。

我們可以這樣簡(jiǎn)單的理解,Stream里有個(gè)操作函數(shù)的集合,每次轉(zhuǎn)換操作就是把轉(zhuǎn)換函數(shù)放入這個(gè)集合中,在Terminal操作的時(shí)候循環(huán)Stream對(duì)應(yīng)的函數(shù)集合,然后對(duì)每個(gè)元素執(zhí)行所有的函數(shù)。

一個(gè)流的操作示例
  int sum = widgets.stream().
         filter(w -> w.getColor() == RED).
         mapToInt(w -> w.getWeight()).
         sum();

stream() 獲取當(dāng)前widgets的source,filter和mapToInt為intermediate操作,進(jìn)行數(shù)據(jù)篩選和轉(zhuǎn)換,最后一個(gè)sun()為terminal操作,對(duì)符合條件的全部widget做重量(weight)的求和。


流的使用詳解

簡(jiǎn)單說(shuō),對(duì)Stream的使用就是實(shí)現(xiàn)了一個(gè)filter-map-reduce過(guò)程,產(chǎn)生一個(gè)最終的結(jié)果,或者導(dǎo)致以個(gè)副作用(side effect)【此處暫未理解】

流的構(gòu)造與轉(zhuǎn)換

構(gòu)造流的幾種常見(jiàn)方法
      /*Stream 初始化的幾種方式*/
       Stream stream = Stream.of("a","b","c");

       /*Arrays*/
       String[] strArray = new String[]{"a","b","c"};
       stream = Stream.of(strArray);
       stream = Arrays.stream(strArray);

       /*Collection*/
       List<String> list = Arrays.asList(strArray);
       stream = list.stream();

對(duì)于基本數(shù)值型,目前有三種對(duì)應(yīng)的包裝類型Stream:

  • IntStream (Stream<Integer>)
  • LongStream (Stream<Long>)
  • DoubleStream (Stream<Double>)

但是裝箱和拆箱會(huì)很耗時(shí),所以特別為這三種基本數(shù)值型提供了對(duì)應(yīng)的Stream。

數(shù)值流的構(gòu)造
        /*Array*/
        String[] strArray1 = (String[]) stream.toArray(String[]::new);
        /*Collection*/
        List<String> list1 = (List<String>) stream.collect(Collectors.toList());
        List<String> list2 = (List<String>) stream.collect(Collectors.toCollection(LinkedList::new));
        Set<String> set = (Set<String>) stream.collect(Collectors.toSet());
        Stack<String> stack = (Stack<String>) stream.collect(Collectors.toCollection(Stack::new));
        /*String*/
        String str = stream.collect(Collectors.joining()).toString();
        /*一個(gè) Stream 只可以使用一次,上面的代碼為了簡(jiǎn)潔而重復(fù)使用了數(shù)次。*/

流的操作

接下來(lái),當(dāng)把一個(gè)數(shù)據(jù)結(jié)構(gòu)包裝成Stream后,就要開(kāi)始對(duì)立面的元素進(jìn)行各類的操作了。常見(jiàn)的操作可以歸類如下。

  • Intermediate:
    map(mapToInt,flatMap等)、filter、distinct,sorted、peek、limit、skip、parallel、sequential、unordered
  • Terminal
    forEach、forEachOrdered、toArray、reduce、collect、min、max、count、anyMatch、allMatch、noneMatch、findFirst、findAny、iterator
  • Short-circuiting:
    anyMatch、allMatch、noneMatch、findFirst、findAny、limit

下面看一下Stream的比較典型的用法。

map/flatMap的作用就是把input Stream的每一個(gè)元素,映射成output Stream的另外一個(gè)元素。
轉(zhuǎn)換大寫(xiě)

List<String> output = wordList.stream().
  map(String::toUpperCase).
  collect(Collectors.toList());

平方數(shù)

    List<Integer> nums = Arrays.asList(1,2,3,4);
    List<Integer> result = nums.stream().
                map(n -> n * n).
                collect(Collectors.toList());

從上面例子可以看出,map生成的是個(gè)1:1的映射,每個(gè)輸入元素,都按照規(guī)則轉(zhuǎn)換成為另外一個(gè)元素。還有一些場(chǎng)景,是一對(duì)多的映射關(guān)系的。這時(shí)需要flatMap

一對(duì)多映射

 Stream<List<Integer>> inputStream = Stream.of(
                Arrays.asList(1),
                Arrays.asList(2,3),
                Arrays.asList(4,5,6));
  Stream<Integer> outputStream = inputStream.
                flatMap(Collection::stream);

flatMap把inputStream中的層級(jí)結(jié)構(gòu)扁平化,就是將最底層元素抽出來(lái)放到一起,最終output的心Stream里面已經(jīng)沒(méi)有l(wèi)ist了 都是直接的數(shù)字。


filter對(duì)原始Stream進(jìn)行某項(xiàng)測(cè)試,通過(guò)測(cè)試的元素被留下來(lái)生成一個(gè)新的Stream。
留下偶數(shù)

 Integer[] sixNums = {1,2,3,4,5,6};
 Integer[] evens = Stream.of(sixNums).
                filter(n -> n%2 == 0).
                toArray(Integer[]::new);

把單詞挑出來(lái)

 BufferedReader reader = new BufferedReader(new ASCIIReader(null,null,null));
        List<String> output1 = reader.lines().
                flatMap(line -> Stream.of(line.split(","))).
                filter(word -> word.length() > 0).
                collect(Collectors.toList());

這段代碼首先把每行的單詞用 flatMap 整理到新的 Stream,然后保留長(zhǎng)度不為 0 的,就是整篇文章中的全部單詞了。


forEach
forEach 方法接收一個(gè)lambda表達(dá)式,然后在每個(gè)元素上執(zhí)行該表達(dá)式。
打印姓名 (java8 和之前版本對(duì)比)

  List<Person> personList = new LinkedList<>();
        /*java8*/
        personList.stream().
                filter(p -> p.getGender() == 0).
                forEach(System.out::println);
        /*java8 之前*/
        for (Person person : personList) {
            if(person.getGender() == 0) {
                System.out.println(person.getName());
            }
        }

對(duì)于一個(gè)人員集合遍歷,找出男性并打印??梢钥闯鰜?lái),forEach是為lambda而設(shè)計(jì)的,保持了最緊湊的風(fēng)格。而且lambda 表達(dá)式本身是可以重用的,非常方便。當(dāng)需要為多核系統(tǒng)優(yōu)化時(shí),可以parallelStream().forEach(),只是此時(shí)原有元素的次序沒(méi)法保證,并行的情況下將改變串行時(shí)操作的行為,此時(shí)forEach本身的實(shí)現(xiàn)不需要調(diào)整,而java8以前的for循環(huán)code可能需要加入額外的多線程邏輯。

但一般認(rèn)為,forEach和常規(guī)的for循環(huán)的差異不涉及到性能,他們僅僅是函數(shù)式風(fēng)格與傳統(tǒng)java風(fēng)格的差別。

另外一點(diǎn)需要注意,forEach是Terminal操作,因此它執(zhí)行后,Stream的元素就被“消費(fèi)”掉了,你無(wú)法對(duì)一個(gè)Stream進(jìn)行兩次Terminal運(yùn)算。例如下面的代碼是錯(cuò)誤的。

stream.forEach(element -> doOneThing(element));
stream.forEach(element -> doAnotherThing(element));

相反,具有相似功能的intermediate操作peek可以達(dá)到上述目的,如下是出現(xiàn)在該API javadoc上的一個(gè)示例。
peek對(duì)每個(gè)元素執(zhí)行操作并返回一個(gè)新的Stream

   Stream.of("one","two","three","four","five").
                filter(x -> x.length() >3).
                peek(x -> System.out.println("filtered value:"+x)).
                map(String::toUpperCase).
                peek(x -> System.out.println("Mapped value:"+x)).
                collect(Collectors.toList());

forEach 不能修改自己包含的本地變量值,也不能用 break/return 之類的關(guān)鍵字提前結(jié)束循環(huán)。


findFirst
這是一個(gè)terminal 兼short-circuiting操作,他總是返回Stream的第一個(gè)元素,或者空。

這里的比較重點(diǎn)是它的返回值類型:Optional。這也是一個(gè)模仿Scala語(yǔ)言中的概念,作為一個(gè)容器,他可能含有某值,或者不包含。使用它的目的是盡可能避免 NullPointException。
Optional的兩個(gè)用例

   public static void print(String text){
    /*Java8*/
        Optional.ofNullable(text).ifPresent(System.out::println);
        /*Java8 之前版本*/
        if(text != null) {
            System.out.println(text);
        }
    }

    public static int getLength(String text) {
        /*Java8*/
        return Optional.ofNullable(text).map(String::length).orElse(-1);
        /*Java8之前*/
//        return text != null ? text.length() : -1;
    }

      String strA = " abcd ",strB = null;
        print(strA);
        print("");
        print(strB);
        System.out.println(getLength(strA));
        System.out.println(getLength(""));
        System.out.println(getLength(strB));

在更復(fù)雜的if(xx != null )的情況中,使用Optional代碼的可讀性更好,而且它提供的是編譯時(shí)檢查,能極大地降低NullPointException 對(duì)程序的影響,或者迫使程序員更早的在編碼階段處理空值的問(wèn)題,而不是留到運(yùn)行時(shí)在發(fā)現(xiàn)和調(diào)試。

Stream中的findAny、max/min、reduce等方法等返回Optional值。還有例如IntStream.average() 返回OptionalDouble等等。


reduce

這個(gè)方法的主要作用是把Stream元素組合起來(lái)。它提供了一個(gè)起始值(種子),然后依照運(yùn)算規(guī)則,和前面的Stream的第一個(gè)、第二個(gè)、第n個(gè)元素組合。從這個(gè)意義上說(shuō),字符串拼接、數(shù)值的sum、min、max、average都是特殊的reduce。例如Stream的sum就相當(dāng)于

        Integer sum = Stream.of(1,2,3,4,5).reduce(0,(a,b) -> a+b); 或
        Integer sum1 = Stream.of(1,2,3,4,5).reduce(0,Integer::sum);

也有沒(méi)有起始值的情況,這時(shí)會(huì)把Stream的前面兩個(gè)元素組合起來(lái),返回的是Optional。
reduce的用例

        /*字符串連接,concat = "ABCD"*/
        String concat = Stream.of("A","B","C","D").
                  reduce("",String::concat);
        /*求最小值,minValue = - 3.0*/
        double minValue = Stream.of(-1.5,1.0,-3.0,-2.0).
                  reduce(Double.MAX_VALUE,Double::min);
        /*求和,sumValue = 10 有起始值*/
        int sumValue = Stream.of(1,2,3,4).reduce(0,Integer::sum);
         /*求和,sumValue = 10 無(wú)起始值*/
         sumValue = Stream.of(1,2,3,4).reduce(Integer::sum).get();
         /*過(guò)濾,字符串連接,concat = "ace"*/
         concat = Stream.of("a","B","c","D","e","F").
                 filter(x -> x.compareTo("Z")> 0).
                 reduce("",String::concat);

上面代碼例如第一個(gè)示例的reduce(),第一個(gè)參數(shù)(空白字符)即為起始值,第二個(gè)參數(shù)(String::concat)為BinaryOperator。這類有起始值的reduce() 都返回具體的對(duì)象。而對(duì)于第四個(gè)示例沒(méi)有起始值的reduce(),由于可能沒(méi)有足夠的元素,返回的是Optional,請(qǐng)留意這個(gè)區(qū)別。


limit/skip

limit返回Stream的前面n個(gè)元素;skip則是扔掉前n個(gè)元素(它是由一個(gè)叫subStream的方法改名而來(lái))。
limit和skip對(duì)運(yùn)行次數(shù)的影響

/*limit和skip對(duì)運(yùn)行次數(shù)的影響*/
    public static void testLimitAndSkip() {
        List<Person> persons = new LinkedList<>();
        for(int i = 0 ;i <10000 ;i++) {
            Person person = new Person();
            person.setGender(1);
            person.setName("name"+i);
            persons.add(person);
        }

       persons.stream().map(Person::getName).
               limit(10).skip(3).collect(Collectors.toList()).
               forEach(System.out::println);

輸出結(jié)果為:


輸出結(jié)果

這是一個(gè)由10000個(gè)元素的Stream 但在short-circuting操作limit和skip的作用下,管道中map操作指定的getName方法的執(zhí)行次數(shù)為limit所限定的10次,而最終返回結(jié)果在跳過(guò)前三個(gè)元素后,只有7個(gè)返回。

有一種情況是limit/skip無(wú)法達(dá)到short-circuiting目的的,就是把它們放在Stream的排序操作后,原因跟sorted這個(gè)intermediate操作有關(guān),此時(shí)系統(tǒng)并不知道Stream破愛(ài)徐厚的次序如何,所以sorted中的操作看上去就像完全沒(méi)有被limit或者skip一樣。即sorted之后加了limit 其實(shí)還是所有數(shù)據(jù)都會(huì)遍歷的。

limit 和 skip 對(duì) sorted 后的運(yùn)行次數(shù)無(wú)影響

   List<Person> list = persons.stream().
                sorted(Comparator.comparing(Person::getName)).
                limit(2).collect(Collectors.toList());
   System.out.println(JSONObject.toJSONString(list));

最后有一點(diǎn)需要注意的是,對(duì)一個(gè)parallel的Stream管道來(lái)說(shuō)沒(méi)如果其元素是有序的,那么limit操作,那么limit的操作的成本會(huì)比較大,因?yàn)樗姆祷貙?duì)象必須是前n個(gè)也有一樣次序的元素。取而代之的策略是取消元素間的次序,或者不要用parallel Stream。


sorted

對(duì)Stream 的排序通過(guò)sorted進(jìn)行,它比數(shù)組的排序更強(qiáng)大之處在于你可以首先對(duì)Stream進(jìn)行各類map、filter、limit、skip甚至distinct來(lái)減少元素?cái)?shù)量后,在排序,這能幫助程序明顯縮短執(zhí)行時(shí)間。

 persons.stream().
                sorted(Comparator.comparing(Person::getName)).
                limit(2).collect(Collectors.toList());

min/max/distinct

min和max的功能也可以通過(guò)對(duì)Stream元素先排序,再findFirest來(lái)實(shí)現(xiàn),但前者性能會(huì)更好,為O(n) 而sorted的成本是O(nlogn).同時(shí)它們作為特殊的reduce方法被獨(dú)立出來(lái)也是因?yàn)榍笞畲笾底钚≈凳呛艹R?jiàn)的操作。

找出最長(zhǎng)的一行長(zhǎng)度

        BufferedReader br = new BufferedReader(new FileReader("D:\\data.log"));
        int longest = br.lines().mapToInt(String::length).max().orElse(0);
        br.close();
        System.out.println(longest);

找出全文的單詞,轉(zhuǎn)小寫(xiě),并排序

        List<String> words = br.lines().
                flatMap(line ->Stream.of(line.split(" "))).
                filter(word -> word.length() > 0).map(String::toLowerCase).
                distinct().sorted().collect(Collectors.toList());
        br.close();
        System.out.println(words);

Match

Stream 有三個(gè)match方法,從語(yǔ)義上說(shuō):

  • allMatch:Stream中全部元素符合傳入的predicate,返回true
  • anyMatch:Stream中只要有一個(gè)元素符合傳入的predicate,返回true
  • noneMatch:Stream中沒(méi)有一個(gè)元素符合傳入的predicate,返回true

它們都不需要遍歷全部元素。例如allMatch只要一個(gè)元素不滿足條件。就skip剩下的所有元素,返回false。
檢測(cè)名稱編號(hào)大于3的

/*檢測(cè)名稱編號(hào)大于3的*/
        List<Person> persons = new LinkedList<>();
        persons.add(new Person(0,"name1"));
        persons.add(new Person(0,"name2"));
        persons.add(new Person(0,"name3"));
        persons.add(new Person(0,"name4"));
        persons.add(new Person(0,"name5"));

        boolean isAllMoreThan3 = persons.stream().
                allMatch(p -> p.getName().compareTo("name3") > 0);
        System.out.println("All more than 3 ? " + isAllMoreThan3);
        boolean isAnyMoreThan3 = persons.stream().
                anyMatch(p -> p.getName().compareTo("name3") >0);
        System.out.println("Is there any more than 3? " + isAnyMoreThan3);

進(jìn)階:字節(jié)生成流

Stream.generate

通過(guò)實(shí)現(xiàn)Supplier接口,你可以自己來(lái)控制流的生成,這種情形通常用于隨機(jī)數(shù)、常量的Stream,或者需要前后元素間的維持著某種狀態(tài)信息的Stream。把Supplier實(shí)例傳遞給Strteam.generate()生成Stream,默認(rèn)都是串行(相對(duì)于parallel而言)但無(wú)序的(相對(duì)于ordered而言)。由于它是無(wú)限的,在管道中,必須利用limit之類的操作限制Stream大小。

使用Supplier生成10個(gè)隨機(jī)整數(shù)

   Random seed = new Random();
        Supplier<Integer> random = seed::nextInt;
        Stream.generate(random).limit(10).
                forEach(System.out::println);
        /*Another way*/
        IntStream.generate(() -> (int)(System.nanoTime()%100)).
                limit(10).forEach(System.out::println);

Stream.generate()還接受自己實(shí)現(xiàn)的Supplier 。例如在構(gòu)造海量測(cè)試數(shù)據(jù)的時(shí)候,用某種自動(dòng)的規(guī)則給每一個(gè)變量賦值;或者依據(jù)公式計(jì)算Stream的每個(gè)元素值。

自實(shí)現(xiàn)Supplier

   Stream.generate(new PersonSupplier()).limit(10).
                forEach(p -> System.out.println(p.getName()));
  private  class PersonSupplier implements Supplier<Person>{
        private int index = 0;
        private Random random = new Random();
        @Override
        public Person get() {
            return new Person(index++,"name"+random.nextInt(100));
        }
    }

輸出結(jié)果:

輸出結(jié)果

Stream.iterate

iterate跟reduce操作很像,接受一個(gè)種子值,和一個(gè)UnaryOperator(例如f)。然后種子值稱為Stream的第一個(gè)元素,f(seed)為第二個(gè),f(f(seed))第三個(gè),以此類推。

使用iterate生成一個(gè)等差數(shù)列

  Stream.iterate(0, m -> m+3).limit(10).forEach(System.out::println);

與Stream.generate相仿,在iterate時(shí)候管道必須有l(wèi)imit這樣的操作來(lái)限制Stream的大小。


進(jìn)階:用Collectors來(lái)進(jìn)行reduction操作

java.util.stream.Collectors類主要作用就是輔助進(jìn)行各類有用的reduction操作,例如轉(zhuǎn)變輸出為Collection,把Stream元素進(jìn)行歸組。

groupingBy/partitioningBy

按照姓名歸組

 Map<String,List<Person>> personGroups = Stream.generate(new PersonSupplier()).
                limit(100).collect(Collectors.groupingBy(Person::getName));

上面的代碼,首先生成100人的信息,然后按照姓名歸組,相同姓名的人放到同一個(gè)list(由于姓名都是不同的,所以每個(gè)person會(huì)存在一個(gè)key中)。

按照指定條件歸組,將name10作為分界

 Map<Boolean,List<Person>> children = Stream.generate(new PersonSupplier()).
            limit(20).collect(Collectors.partitioningBy(person -> 
            person.getName().compareTo("name10") > 0));

在使用條件 person.getName().compareTo("name10") > 0進(jìn)行分組后可以看到,小于name10的十一組,大于name10的是另外一組。partitioningBy其實(shí)是一種特殊的groupingBy,它依照條件測(cè)試的是否兩種結(jié)果來(lái)構(gòu)造返回的數(shù)據(jù)結(jié)構(gòu),get(true)和get(false)能即為全部的元素對(duì)象。


結(jié)束語(yǔ)

總之Stream的特性可以歸納為:

  • 不是數(shù)據(jù)結(jié)構(gòu)
  • 他沒(méi)有內(nèi)部存儲(chǔ),它只是用操作管道從source(數(shù)據(jù)結(jié)構(gòu),數(shù)組、generator function、IO channel)抓取數(shù)據(jù)。
  • 它也絕不修改自己所封裝的底層數(shù)據(jù)結(jié)構(gòu)的數(shù)據(jù)。例如Stream的filter操作會(huì)產(chǎn)生一個(gè)不包含被過(guò)濾元素的心Stream,而不是從source刪除那些元素。
  • 所有的Stream的操作必須以lambda表達(dá)式為參數(shù)
  • 不支持索引訪問(wèn)
  • 可以請(qǐng)求第一個(gè)元素,但無(wú)法請(qǐng)求第二個(gè)第三個(gè)或最后一個(gè)元素。
  • 很容易生成數(shù)組或者List
  • 惰性化
  • 很多Stream操作是向后延遲的,一直到它弄清楚了最后需要多少數(shù)據(jù)才會(huì)開(kāi)始。
  • Intermediate操作永遠(yuǎn)是惰性化的。
  • 并行能力
  • 當(dāng)一個(gè)Stream是并行化的,就不需要寫(xiě)多線程代碼,所有對(duì)它的操作會(huì)自動(dòng)并行進(jìn)行。
  • 可以是無(wú)限的。集合有固定大小,Stream則不必。limit(n)和findFirst()這類short-circuting操作可以對(duì)無(wú)限的Stream進(jìn)行運(yùn)算并很快完成。

示例中源碼下載

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容