
在前面一個小節(jié)中,我們已經(jīng)學習了行為參數(shù)化以及Lambda表達式,通過Lambda表達式,可以使得代碼更加簡潔,尤其是當一個方法只需要使用一次的時候,然而,如果Java8中只有Lambda表達式的話,那還是不足以讓人感到興奮的,個人感覺,Java8中最有意思,也是最方便的功能,莫過于??Stream?
?了
??Stream?
?可以翻譯為流,實際上其操作也是,流操作是Java8中引入的新功能,提供了更加強大的數(shù)據(jù)迭代處理方式,通過流式寫法,提供了簡潔的語法,主要注意的是??Stream?
?需要配合Lambda表達式來使用,這更加體現(xiàn)了行為參數(shù)化的思想,Java8通過將既定的操作封裝好,同時,將對應的具體行為留給用戶,極大地提高了操作的效率。
??Stream?
?的出現(xiàn),可以說是用于替代傳統(tǒng)的容器操作的,在傳統(tǒng)的容器操作中,當需要對容器中的某些元素進行操作的時候,我們需要迭代容器,然后篩選出合適的對象,然后再將其存放到另外的容器中,從上面的描述中,可以看到,其中的很大一部分操作:迭代容器,篩選對象,重新存放基本都是固定的,而每次都進行手動操作,顯然是比較繁瑣的,??Stream?
?則提供了更加便捷的操作,只需要通過對應的操作模式,然后給出對應的條件,即可實現(xiàn)對既定元素的操作。
【資料圖】
為了下面的操作方便,我們先構造需要的元素
// User對象class User { private Integer id; private String name; private Integer age; // 省略set,get,toString方法}// 構造數(shù)據(jù)public static ListgenerateUserData() { Random random = new Random(); List users = new ArrayList<>(); for (int i = 0; i < 1000; i++) { users.add(new User(i, "user" + i, random.nextInt(100))); } return users;}
假設現(xiàn)在有一個場景,我們需要從上面的列表中選取年齡大于20歲的對象,在傳統(tǒng)的容器操作中,一般我們會這樣操作
public ListgetUserOlderThan20() { List users = generateUserData(); List result = new ArrayList<>(); for (User user : users) { if (user.getAge() > 20 ) { result.add(user); } } return result;}
而在Java8中,我們可以用更加簡潔的方式來實現(xiàn)上面的操作
public ListgetUserOlderThan20() { List users = generateUserData(); List result = users.stream() .filter(user -> user.getAge() > 20) .collect(Collectors.toList()); return result;}
或者上面的案例看上去并沒有那么有優(yōu)勢,那么我們來看下下面的案例,根據(jù)年齡對用戶進行分組,年齡在1-30為年輕人,31-60為中年人,60以上為老年人(例子例子,沒有實際價值)
傳統(tǒng)的操作,我們需要如下操作
public void groupUser() { Listusers = generateUserData(); Map > userGroup = new HashMap<>(); for (User user : users) { if (user.getAge() > 0 && user.getAge() <= 30) { List young = userGroup.get("young"); if (young == null) { young = new ArrayList<>(); userGroup.put("young", young); } userGroup.get("young").add(user); }else if (user.getAge() <= 60) { List middle = userGroup.get("middle"); if (middle == null) { middle = new ArrayList<>(); userGroup.put("middle", middle); } userGroup.get("middle").add(user); }else { List old = userGroup.get("old"); if (old == null) { old = new ArrayList<>(); userGroup.put("old", old); } userGroup.get("old").add(user); } } System.out.println(userGroup);}
可以看到,上面的操作還是挺繁瑣的,而且比較容易出錯,而在Java8中,我們則可以采用如下操作
public void testStream() { Listusers = generateUserData(); Map > result = users.stream() .collect(Collectors.groupingBy( user -> { if (user.getAge() > 0 && user.getAge() <= 30) { return "young"; } else if (user.getAge() <= 60) { return "middle"; } else { return "old"; }} )); System.out.println(result);}
可以看到,代碼量以及自描述性的對比還是挺明顯的,??Stream?
?配合??Lambda?
?表達式,可以使得之前比較繁瑣的容器操作,變得非常簡單,而且,代碼本身的自解釋性也更強
在前面我們已經(jīng)見識到了??Stream?
?本身的特點--流式操作以及方便性,接下來我們來詳細學習??Stream?
?的用法。
??Stream?
?的操作可以分為兩種,一種是中間操作,例如前面的??filter()?
?操作,一種是結束操作,例如前面的??collect()?
?操作,每一個中間操作,都返回一個??Stream?
?,經(jīng)過本次處理之后的??Stream?
?,結束操作則產(chǎn)生終結,其結果要么是數(shù)字,要么是字符串,要么是集合等等,總之就不再是??Stream?
?,也就是說,一個??Stream?
?可以有多個中間操作,但只能有一個結束操作
中間操作
比較常用的幾種中間操作列舉如下,更多的內容參考API即可
??filter()?
?,過濾操作,入?yún)??Predicate super T> predicate?
???limit()?
?,限制操作,入?yún)??long maxSize?
???skip()?
?,跳過操作,入?yún)??long n?
???distinct()?
?,去重操作,沒有入?yún)ⅲ讓邮褂玫氖??Set?
?進行去重??sorted()?
?,排序操作,可以傳入自定義的比較器??Comparator super T> comparator?
???peek()?
?,檢查操作,用于調試操作,入?yún)??Consumer super T> action?
??map()?
?,將Stream中的元素映射為其他元素,入?yún)??Function super T, ? extends R> mapper?
??mapToDouble()?
?,將Stream轉為??DoubleStream?
?,避免裝箱機制所帶來的開銷??mapToLong()?
?,將Stream轉為??LongStream?
?,避免裝箱機制所帶來的開銷??mapToInt()?
?,將Stream轉為??IntStream?
?,避免裝箱機制所帶來的開銷??flatMap()?
?,將多個Stream轉為一個,注意與??map()?
?的區(qū)別,入?yún)??Function super T, ? extends Stream extends R>> mapper?
?結束操作
比較常用的幾個結束操作列舉如下,更多的內容參考API即可
??count()?
?,統(tǒng)計元素個數(shù)??forEach()?
?,對每個元素執(zhí)行操作,入?yún)??Consumer super T> action?
???findFirst()?
?,獲取第一個元素??findAny()?
?,獲取任意一個元素??anyMatch()?
?,檢查元素是否至少有一個匹配,入?yún)??Predicate super T> predicate?
???allMatch()?
?,檢查所有元素是否都匹配,入?yún)??Predicate super T> predicate?
??collect()?
?,將所有內容收集起來,入?yún)??Collector super T, A, R> collector?
?,JDK中提供了眾多的??Collector?
?的實現(xiàn),所以,基本上不用自己實現(xiàn)?groupingBy()?
?,將內容進行分組,有三個不同的版本??groupingBy(Function super T, ? extends K> classifier)?
?,僅能進行一次分組??groupingBy(Function super T, ? extends K> classifier, Collector super T, A, D> downstream)?
?,注意第二個參數(shù)可以是另一個??Collector?
?,也就是說,可以通過多次的復合,達到多次分組,或者分組后再進行其他的操作??groupingBy(Function super T, ? extends K> classifier,Supplier mapFactory, Collector super T, A, D> downstream)?
?,自己提供一個容器,而不是使用默認的容器??counting()?
?,等價于前面的??Stream.count()?
???partitioningBy()?
?精簡版的??groupingBy()?
?,僅能支持??true?
?、??false?
?兩種分組??joining()?
?,字符串連接,需要注意,如果Stream的內容本身不是字符串流,則需要先??map()?
?操作一下,將其轉為字符串流,可以指定分隔符,前綴,后綴??toList()?
?,將結果合并為List??toSet()?
?,將結果合并為Set??toMap()?
?,將結果轉為Map??toConcurrentMap()?
?,將結果轉為并發(fā)Map?reduce()?
?,根據(jù)條件合并結果,可以說,上面的所有結束操作,基本上都可以通過??reduce()?
?來實現(xiàn),??reduce?
?有三個不同形式的參數(shù),當JDK所提供的合并操作不滿足需求時,可以通過??reduce?
?來實現(xiàn)自定義的合并操作??T reduce(T identity, BinaryOperator accumulator)?
???Optional reduce(BinaryOperator accumulator)?
??? U reduce(U identity, BiFunction accumulator, BinaryOperator combiner)?
?Stream操作實例
為了更好地理解上面的內容,我們通過幾個小例子來實際操作一下
// 打印出年齡在30歲以上的所有用戶 users.stream() .filter(user -> user.getAge() > 30) .forEach(System.out::println); // 如果換成 .count(),則是統(tǒng)計用戶的個數(shù) // 分組并且統(tǒng)計各個分組的人數(shù) Mapcollect = users.stream() .collect(groupingBy(user -> { if (user.getAge() <= 30) { return "young"; } else if (user.getAge() <= 60) { return "middle"; } else { return "old"; } }, counting())); // 分組并且去重 Map > collect = users.stream() .collect(groupingBy(user -> { if (user.getAge() <= 30) { return "young"; } else if (user.getAge() <= 60) { return "middle"; } else { return "old"; } }, toSet()));
關于Stream的介紹,大致就到這里了,為了更好地掌握Stream,需要在實際使用中多加練習,多加研究才是
本小節(jié)主要學習了Stream的內容,通過對比Stream與傳統(tǒng)的Collection操作,可以看出,通過Stream來操作容器,代碼將變得更加簡潔,而且,其可閱讀行也更強,出錯的概率也會更低,畢竟不用再自己關心迭代的過程,最后,通過幾個簡單的小例子,展示了Stream中兩種不同的操作,中間操作以及結束操作,當然,關于Stream的更多內容,還是需要在實際使用中不斷發(fā)現(xiàn),不斷研究,加油。