Java8-聚合操作


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)));

 

上面的是計算每個性別的年齡平均值。

還有其他很多類型的聚合操作,一個語法的熟練掌握還是需要靠實踐來完成....

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM