上一篇我們介紹了Strem的概念與實際的一些操作,本篇我們繼續來學習Stream的另一個重要操作,分組與分割槽。
我們在上一篇介紹Stream的操作時,會經常使用到Collectors這個類,這個類實際上是一個封裝了很多常用的匯聚操作的一個工廠類。我們之前用到過
//將結果匯聚到ArrayList中
Collectors.toList();
//將結果匯聚到HashSet中
Collectors.toSet();
以及更為通用的
//將結果匯聚到一個指定型別的集合中
Collectors.toCollection(Supplier<C> collectionFactory);
Stream分組
在實際開發中,對於將一個集合的內容進行分組或分割槽這種需求也非常常見,所以我們繼續學習下Collectors類中的groupingBy和partitioningBy方法。
public static Collector groupingBy(Function<? super T, ? extends K> classifier){
//...
}
groupingBy接收一個Function型別的變數classifier,classifier被稱作分類器,收集器會按著classifier作為key對集合元素進行分組,然後返回Collector收集器物件,假如現在有一個實體Student
public class Student {
private String name;
private int score;
private int age;
public Student(String name,int score,int age){
this.name = name;
this.score = score;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
我們現在按Student的name進行分組,如果使用sql來表示就是select * from student group by name; 再看下使用Stream的方式
Map<String, List<Student>> collect = students.stream().collect(Collectors.groupingBy(Student::getName));
這裡我們使用方法引用(類名::例項方法名)替代lambda表示式(s -> s.getName())的方式來指定classifier分類器,使集合按Student的name來分組。
注意到分組後的返回型別是Map<String, List<Student>>,結果集中會將name作為key,對應的Student集合作為value返回。
那如果按name分組後,想求出每組學生的數量,就需要藉助groupingBy另一個過載的方法
public static Collector groupingBy(Function<? super T, ? extends K> classifier,Collector<? super T, A, D> downstream){
//...
}
第二個引數downstream還是一個收集器Collector物件,也就是說我們可以先將classifier作為key進行分組,然後將分組後的結果交給downstream收集器再進行處理
//按name分組 得出每組的學生數量 使用過載的groupingBy方法,第二個引數是分組後的操作
Map<String, Long> collect1 = students.stream().collect(Collectors.groupingBy(Student::getName, Collectors.counting()));
Collectors類這裡也幫我們封裝好了用於統計數量的counting()方法,這裡先了解一下counting()就是將收集器中元素求總數即可,後續我們會再深入原始碼學習。
我們還可以對分組後的資料求平均值
Map<String, Double> collect2 = students.stream().collect(Collectors.groupingBy(Student::getName, Collectors.averagingDouble(Student::getScore)));
averagingDouble方法接收一個ToDoubleFunction引數
@FunctionalInterface
public interface ToDoubleFunction<T> {
/**
* Applies this function to the given argument.
*
* @param value the function argument
* @return the function result
*/
double applyAsDouble(T value);
}
ToDoubleFunction實際上也是Function系列函式式介面中的其中一個特例,接收一個引數,返回Double型別(這裡是接收一個Student返回score)。因為分組後的集合中每個元素是Student型別的,所以我們無法直接對Student進行求平均值
//虛擬碼
Collectors.averagingDouble(Student))
所以需要將Student轉成score再求平均值,Collectors.averagingDouble(Student::getScore))。
Stream分割槽
針對上面的Student,我們現在再加一個需求,分別統計一下及格和不及格的學生(分數是否>=60)
這時候符合Stream分割槽的概念了,Stream分割槽會將集合中的元素按條件分成兩部分結果,key是Boolean型別,value是結果集,滿足條件的key是true,我們看下示例。
Map<Boolean, List<Student>> collect3 = students.stream().collect(Collectors.partitioningBy(student -> student.getScore() >= 60));
System.out.println(collect3.get(true));//輸出及格的Student
System.out.println(collect3.get(false));//輸出不及格的Student
partitioningBy方法接收一個Predicate作為分割槽判斷的依據,滿足條件的元素放在key為true的集合中,反之放在key為false的集合中
//partitioningBy方法
public static Collector partitioningBy(Predicate<? super T> predicate) {
return partitioningBy(predicate, toList());
}