JDK1.8新特性(六):Stream的終極操作,輕鬆解決集合分組、彙總等複雜操作

在這裏插入圖片描述
前期回顧:
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分別爲truefalseMap中,從而我們能夠得到符合和不符合的分組新集合。
  • 最值:按照某個屬性查找最大、最小元素。
  • 累加、彙總:用來完成累加計算、數據彙總(總數、總和、最小值、最大值、平均值)。
  • 連接:將元素以某種規則連接起來。
  • ……

二、實戰演練

1. 類型歸類

將集合中元素按照類型、條件過濾等歸類,存放到指定類型的新集合,ListMapSetCollection或者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));

如果我們不想返回MapvalueList怎麼辦?實際上可以按照下面的方式分組:

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分別爲truefalseMap中,從而我們能夠得到符合和不符合的分組新集合。

比如,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的使用過程中,你還遇到哪些好用、好玩的終極操作呢?

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