前期回顧:
JDK1.8新特性(一):JDK1.8究竟有哪些新特性呢
JDK1.8新特性(二):爲什麼要關注JDK1.8
JDK1.8新特性(三):Lambda表達式,讓你愛不釋手
JDK1.8新特性(四):函數式接口
JDK1.8新特性(五):Stream,集合操作利器,讓你好用到飛起來
上一篇JDK1.8新特性(五):Stream,集合操作利器,讓你好用到飛起來,主要講解了關於Stream的基本操作,可以輕鬆擺脫**“遍歷、再遍歷、再運算”**等複雜操作,但Stream遠遠不止這些。本文將講述關於Stream的終極操作,讓你輕鬆解決集合的分組、彙總等操作,讓其他同事對你刮目相看。
一、Collectors
java.util.stream.Collectors
,是從JDK1.8開始新引入的一個類。從源碼的類註釋上,我們可以知道:Collectors實現了各種有用歸約的操作,例如類型歸類到新集合、根據不同標準彙總元素等。透過示例,能讓我們眼前一亮,短短的一行代碼卻能處理如此強大、複雜的功能:彙總、拼接、累加計算、分組等。
切記,不要用錯哦,是java.util.stream.Collectors
,不是java.util.Collections
。
/**
* Implementations of {@link Collector} that implement various useful reduction
* operations, such as accumulating elements into collections, summarizing
* elements according to various criteria, etc.
*
* <p>The following are examples of using the predefined collectors to perform
* common mutable reduction tasks:
*
* <pre>{@code
* // Accumulate names into a List
* List<String> list = people.stream().map(Person::getName).collect(Collectors.toList());
*
* // Accumulate names into a TreeSet
* Set<String> set = people.stream().map(Person::getName).collect(Collectors.toCollection(TreeSet::new));
*
* // Convert elements to strings and concatenate them, separated by commas
* String joined = things.stream()
* .map(Object::toString)
* .collect(Collectors.joining(", "));
*
* // Compute sum of salaries of employee
* int total = employees.stream()
* .collect(Collectors.summingInt(Employee::getSalary)));
*
* // Group employees by department
* Map<Department, List<Employee>> byDept
* = employees.stream()
* .collect(Collectors.groupingBy(Employee::getDepartment));
*
* // Compute sum of salaries by department
* Map<Department, Integer> totalByDept
* = employees.stream()
* .collect(Collectors.groupingBy(Employee::getDepartment,
* Collectors.summingInt(Employee::getSalary)));
*
* // Partition students into passing and failing
* Map<Boolean, List<Student>> passingFailing =
* students.stream()
* .collect(Collectors.partitioningBy(s -> s.getGrade() >= PASS_THRESHOLD));
*
* }</pre>
*
* @since 1.8
*/
換句話說,Collectors結合Stream將成爲集合的終極操作,其中,包括:
- 類型歸類:將集合中元素按照類型、條件過濾等歸類,存放到指定類型的新集合。
- 分組:按照條件對元素進行分組,和SQL中
group by
的用法有異曲同工之妙。 - 分區:分組的特殊情況,實質是在做二分組,將符合條件、不符合條件的元素分組到兩個
key
分別爲true
和false
的Map
中,從而我們能夠得到符合和不符合的分組新集合。 - 最值:按照某個屬性查找最大、最小元素。
- 累加、彙總:用來完成累加計算、數據彙總(總數、總和、最小值、最大值、平均值)。
- 連接:將元素以某種規則連接起來。
- ……
二、實戰演練
1. 類型歸類
將集合中元素按照類型、條件過濾等歸類,存放到指定類型的新集合,List
、Map
、Set
、Collection
或者ConcurrentMap
。涉及以下方法:
-
Collectors.toList()
-
Collectors.toMap()
-
Collectors.toSet()
-
Collectors.toCollection()
-
Collectors.toConcurrentMap()
一般都作爲終止操作符cololect
的參數來使用,並伴隨着流的結束。
常用於收集、篩選出集合(複雜集合)中的符合條件的數據,並存放於對應類型的新集合中,便於後續實際業務邏輯處理。
比如,將名字類型歸類存在到List<String>
集合中:
List<String> list = allPeoples.stream().map(People::getName).collect(Collectors.toList());
2. 分組
按照條件對元素進行分組,和 SQL 中的 group by
用法有異曲同工之妙,通常也建議使用Java代碼進行分組處理以減輕數據庫SQL壓力。
分組涉及以下方法:
-
Collectors.groupingBy(…):普通分組。
-
Collectors.groupingByConcurrent(…):線程安全的分組。
分組後,返回的是一個Map
集合,其中key
作爲分組對象,value
作爲對應分組結果。
比如,考慮到People集合中可能會存在同齡人,將集合按照年齡進行分組:
Map<Integer, List<People>> groupingByAge = allPeoples.stream().collect(Collectors.groupingBy(People::getAge));
如果我們不想返回Map
的value
爲List
怎麼辦?實際上可以按照下面的方式分組:
Map<Integer, Set<People>> groupingByAge2 = allPeoples.stream().collect(Collectors.groupingBy(People::getAge, Collectors.toSet()));
考慮到同步安全問題時,怎麼辦?
採用線程安全的分組Collectors.groupingByConcurrent(…)
,於是:
Map<Integer, List<People>> groupingByAge3 = allPeoples.stream().collect(Collectors.groupingByConcurrent(People::getAge));
3. 分區
是分組的特殊情況,採用Collectors.partitioningBy(…)
方法來完成。
該方法實質是在做二分組,將符合條件、不符合條件的元素分組到兩個key
分別爲true
和false
的Map
中,從而我們能夠得到符合和不符合的分組新集合。
比如,People集合中人名有中文名,也有英文名,將人名按照中、英文名進行分區:
Map<Boolean, List<People>> partitioningByName = allPeoples.stream().collect(Collectors.partitioningBy(people -> people.getName().matches("^[a-zA-Z]*")));
// 獲取英文名集合
List<People> englishNames = partitioningByName.get(true);
// 獲取中文名集合
List<People> chineseNames = partitioningByName.get(false);
4. 最值
按照某個屬性查找出最大或最小值元素,並且基於Comparator
接口來對其進行比較,返回一個Optional對象,並結合Optional.isPresent()
判斷並取得最大或最小值。
涉及以下方法:
- Collectors.maxBy(…):最大值。
- Collectors.minBy(…):最小值。
比如,找到People集合中最大、最小年齡的人:
// 查找最大年齡的人
Optional<People> maxAgeOptional = allPeoples.stream().collect(Collectors.maxBy(Comparator.comparingInt(People::getAge)));
People maxAgePeople = null;
if (maxAgeOptional.isPresent()) {
maxAgePeople = maxAgeOptional.get();
}
// 查找最小年齡的人
Optional<People> minAgeOptional = allPeoples.stream().collect(Collectors.minBy(Comparator.comparingInt(People::getAge)));
People minAgePeople = null;
if (minAgeOptional.isPresent()) {
minAgePeople = minAgeOptional.get();
}
5. 累加、彙總
用來完成累加計算、數據彙總(總數、總和、最小值、最大值、平均值)操作。
計算集合某個屬性的總和,類似與SQL中的sum
函數。
涉及以下方法:
- Collectors.summingInt/Double/Long(…):按照某個屬性求和。
- Collectors.summarizingInt/Double/Long(…):按照某個屬性的數據進行彙總,得到其總數、總和、最小值、最大值、平均值。
比如,計算全體人員的薪資總和:
int salaryTotal = allPeoples.stream().collect(Collectors.summingInt(People::getSalary));
如果想要得到全體人員的薪資數據整體情況(包括總數、總和、最小值、最大值、平均值),怎麼辦呢?
難道分別要搞多個Stream流嗎?
當然,沒有這麼麻煩,只需Collectors.summarizingInt
方法就可輕鬆搞定。
// 輸出:IntSummaryStatistics{count=10, sum=45000, min=2000, average=4500.000000, max=7000}
IntSummaryStatistics intSummaryStatistics = allPeoples.stream().collect(Collectors.summarizingInt(People::getSalary));
6. 連接
將元素以某種規則連接起來,得到一個連接字符串。
涉及以下方法:
- Collectors.joining():字符串直接連接。
- Collectors.joining(CharSequence delimiter):按照字符
delimiter
進行字符串連接。 - Collectors.joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix):按照前綴
prefix
,後綴suffix
,並以字符delimiter
進行字符串連接。
比如,將People集合中所有名字按照某種連接符進行字符串連接:
// 輸出:xcbeyondNikiXiaoMing超哥小白小紅LucyLily超級飛俠樂迪
String namesStr1 = allPeoples.stream().map(People::getName).collect(Collectors.joining());
// 輸出:xcbeyond,Niki,XiaoMing,超哥,小白,小紅,Lucy,Lily,超級飛俠,樂迪
String namesStr2 = allPeoples.stream().map(People::getName).collect(Collectors.joining(","));
// 輸出:[xcbeyond,Niki,XiaoMing,超哥,小白,小紅,Lucy,Lily,超級飛俠,樂迪]
String namesStr3 = allPeoples.stream().map(People::getName).collect(Collectors.joining(",", "[", "]"));
三、總結
本文,只是針對JDK1.8java.util.stream.Collectors
中最好用的操作進行單獨舉例說明,不涉及嵌套、複合、疊加使用,實際業務場景下可能會涉及到多種操作的疊加、組合使用,需按需靈活使用即可。
如果你熟悉了上面這些操作,在面對複雜集合、處理複雜邏輯時,就會更加得心應手。尤其是分組、彙總,簡直是太好用了。
在JDK1.8的使用過程中,你還遇到哪些好用、好玩的終極操作呢?