Java聚合操作(Aggregate Operations)是對一堆數據進行處理的新的操作方法,我們知道,如果想對一堆數據進行處理,比如一個List對象中的數據進行處理,傳統的操作就是遍歷List數據然后進行處理;現在有一種新的可以提供相同功能的操作方法,就是聚合操作(Aggregate Operations),它常與與lambda表達式綁定使用,在lambda表達式使用總結一節已經使用到了,這里舉一個例子,如下代碼,要求打印出List列表中性別為MALE的對象信息:
public class Main { public static void main(String[] args) { Person[] pers = {new Person(12, "h1",Person.Sex.FEMALE), new Person(23, "h2",Person.Sex.MALE), new Person(14, "h3",Person.Sex.FEMALE), new Person(2, "h4",Person.Sex.MALE)}; List<Person> personList= Arrays.asList(pers); //傳統的for-each操作 for(Person p: personList){ if(p.getGender()== Person.Sex.MALE){ System.out.print(p+" "); } } System.out.println("\n--------分割線--------"); //聚合操作 personList.stream().filter(obj->obj.getGender()==Person.Sex.MALE).forEach(obj->System.out.print(obj+" ")); } } @Data @ToString class Person{ int age; String name; Sex gender; public Person(int age,String name,Sex gender){ this.age=age; this.name=name; this.gender=gender; } public Person(int age,String name){ this.age=age; this.name=name; } public enum Sex { MALE,FEMALE } }
上面的代碼中,聚合操作的代碼只一行就解決問題了,聚合操作涉及到兩個概念:管道(pipeline)和流(stream),解釋如下
管道(pipeline)是作用在源數據上的一些聚合操作,可以理解為數據源和多個聚合操作符號的組合,如上面的filter、forEach,一個管道包含以下內容:
1、一個數據源:可以是一個collection,array,生成函數,或則IO channel,本例中是一個集合。
2、0個或則多個中間操作,例如filter,可以產生一個新的流。
3、一個終止操作,例如forEach,產生一個非流的結果(non-stream result)。
流(stream)是一個個的元素,它不是某種數據結構,不用於存儲數據,它的作用就是通過管道從數據源獲取並傳輸數據,在上面的代碼中集合personList通過調用stream()方法生成一個流。
歸約操作:返回一個結果對象的操作,例如下面的示例:
double average = roster
.stream()
.filter(p -> p.getGender() == Person.Sex.MALE)
.mapToInt(Person::getAge)
.average()
.getAsDouble();
上面的示例中,計算出所有男性對象的一個平均年齡,在聚合操作(aggregate operation)中有很多終止操作(terminal operation)只返回一個結果,這樣的操作就是歸約操作(reduction operation),除了返回一個值之外,還有一些歸約操作返回一個集合對象。JDK提供了一個一般性的歸約操作:reduce和collect方法。具體使用如下:
1、reduce方法,以一個示例引用使用方法,例如求所有人的年齡和:
Integer totalAge = roster
.stream()
.mapToInt(Person::getAge)
.sum();
Integer totalAgeReduce = roster
.stream()
.map(Person::getAge)
.reduce(
0,
(a, b) -> a + b);
上面的示例中的兩種計算方法得出的結果是一樣的,更多的使用案例可參考:https://www.cnblogs.com/qinhao517/p/9197885.html
2、collect方法,reduce是產生一個新值,而collect方法是來修改一個已經存在的值,還是上面的求平均年齡為例,我們可以創建一個對象用於存儲計算結果:
class Averager implements IntConsumer{
private int total = 0;
private int count = 0;
public double average() {
return count > 0 ? ((double) total)/count : 0;
}
public void accept(int i) { total += i; count++; }
public void combine(Averager other) {
total += other.total;
count += other.count;
}
}
Averager averageCollect = roster.stream()
.filter(p -> p.getGender() == Person.Sex.MALE)
.map(Person::getAge)
.collect(Averager::new, Averager::accept, Averager::combine);
System.out.println("Average age of male members: " +
averageCollect.average());
上面的計算中,collect傳入了三個參數,返回了一個Averager對象,傳入的三個參數分別是:
supplier:是一個工廠函數,用於創建新的實例對象
accumulator:累加器函數用於將一個流元素合成一個結果容器,在這個示例中,它通過給count變量加一和將流元素(stream element)中的值(getAge方法獲取到的年齡)加給total來修改Averager的結果
combiner:合並函數拿出兩個結果容器(result container)來合並他們的內容,在這個示例中,它通過將其他結果容器的count值和total值累加到自己的(Averager::new 創建的對象)元素中來生成最終的結果
上面的示例中,filter方法過濾出所有男性的對象,map方法通過getAage獲取年齡將人的對象單個的流對象映射成一個年齡值,collect方法通過這些年齡值的運算返回一個最終的結果,結果值是利用Averager對象最為媒介來返回的。
collect還可以將對象中的某個值抽取出來作為一個list返回給用戶:
List<String> namesOfMaleMembersCollect = roster
.stream()
.filter(p -> p.getGender() == Person.Sex.MALE)
.map(p -> p.getName())
.collect(Collectors.toList());
除了上面的操作,下面還有其他的示例:
Map<Person.Sex, List<Person>> byGender =
roster
.stream()
.collect(
Collectors.groupingBy(Person::getGender));
上面的示例中,是通過性別,將不同的對象放到各自性別的List集合中,那我只想獲取不同性別的對象的名字呢?
Map<Person.Sex, List<String>> namesByGender =
roster
.stream()
.collect(
Collectors.groupingBy(
Person::getGender,
Collectors.mapping(
Person::getName,
Collectors.toList())));
也可以獲取每個性別的所有對象的年齡之和:
Map<Person.Sex, Integer> totalAgeByGender =
roster
.stream()
.collect(
Collectors.groupingBy(
Person::getGender,
Collectors.reducing(
0,
Person::getAge,
Integer::sum)));
上面的操作更復雜一些,reducing函數進行歸約操作,上面的0是一個計算的基礎標識,在此標識的基礎上進行操作(operation),至於什么操作,第三個參數的Integer::sum給出了答案,Person::getAge對應的是一個mapper操作,該mapper利用getAge方法抽取出年齡。
Map<Person.Sex, Double> averageAgeByGender = roster
.stream()
.collect(
Collectors.groupingBy(
Person::getGender,
Collectors.averagingInt(Person::getAge)));
上面的是計算每個性別的年齡平均值。
還有其他很多類型的聚合操作,一個語法的熟練掌握還是需要靠實踐來完成....
