Java 8 聚合操作詳解

jopen 9年前發布 | 22K 次閱讀 Java 8 Java開發

Oracle在2014年3月19日如期發布了Java 8。Java 8版本被認為是具有里程碑意義的一個版本,Oracle在該版本中添加了許多新特性,包括Lambda表達式、方法引用、加強了安全等等。

在眾多的新特性中,聚合操作(Aggregate Operations)是針對集合類的一個比較大的變化。通過聚合操作,開發者可以更容易地使用Lambda表達式,并且更方便地實現對集合的查找、遍歷、過濾以及常見計算等。

聚合操作與Java 8中的Lambda表達式、方法引用等新特性是相關的,一般一起組合使用,但這里只說明聚合操作的使用,下面就聚合操作的使用進行簡單說明。

集合類的層次結構

集合類是Java語言提供的輔助類,是一種較為通用的數據結構,如Map、Set、List等。Java中集合類層次關系如下:

圖 1

如上圖,Collection是主要集合類的接口,其子接口(具化接口)有Deque、Queue、Set、List等。

Map是另一種類型的集合,以Key、Value的鍵值對存儲數據集。

在Java 8中,在java.util.Collection接口中添加了如下方法:

Stream<E> stream() {
    return StreamSupport.stream(spliterator(), false);
}

stream()方法的可見性修飾符為default,這又是Java 8的新特性。在接口中(Collection為interface),本不需要(也不能)進行方法實現,但引入default修飾后就不同了。開發者不但 可以進行方法的實現,而且還不用考慮向后兼容的問題。關于Default Method的詳細解釋,讀者可以參考Java 8的官方文檔。

正是stream方法引出了集合類的聚合操作。

[注意]

Map接口中并沒有stream()方法,但是Map的values()和keySet()均返回集合對象,在集合對象上當然是可以使用stream()方法的。

聚合操作實例

為說明聚合操作的使用,首先定義一個數據元素類Person,如下:

import java.time.LocalDate;

public class Person {
        String name;
        LocalDate birthday;
        Sex gender;
        String emailAddress;

        public int getAge() {
            return LocalDate.now().getYear() - birthday.getYear();
        }

        public void setBirthday(LocalDate birthday){
            this.birthday = birthday;
        }

        public void setGender(Sex sex){
            this.gender = sex;
        }

        public void printPerson() {
            System.out.println("The name is " + name);
        }

        public Sex getGender(){
            return gender;
        }

        public enum Sex {
            MALE, FEMALE
        }
    }

在Java 8以前的版本中,對Person集合的遍歷往往采用以下方式:

Set<Person> persons = new HashSet<Person>();

//傳統遍歷方式 for (Person person : persons) { if (person.getAge() > 18) { System.out.println(person.name + ” is elder than 18.”); } }

同樣的功能,在Java 8中使用聚合操作,可以實現如下:

//使用聚合操作
persons.stream().filter(new Predicate<Person>() {
       @Override
        public boolean test(Person person) {
            if (person.getAge() > 18) {
                return true;
            } else {
                return false;
            }
        }
    }).forEach(new Consumer<Person>() {
        @Override
        public void accept(Person person) {
            System.out.println(person.name + " is elder than 18.");
        }
    });

首先,在集合對象persons上調用stream()方法(聚合操作),取得person對象的數據集(elements),然后調用聚合操作filter()對集合中的元素進行過濾,再調用forEach()完成對符合條件的person的打印。

Predicate和Consumer為Java 8中定義的函數接口(Functional Interface),在java.util.function包下面,函數接口也是Java 8的新特性。在上述代碼中,使用了兩個匿名類分別對Predicate和Consumer進行了實現,這兩個接口都只有一個方法,這也是函數接口的特征之 一。

上述代碼中的寫法還是比較繁瑣的,為進一步簡化,可以使用Lambda表達式實現,如下:

// 使用聚合操作及Lambda
    persons.stream()
        .filter(p -> p.getAge() >= 18)
        .forEach(p -> System.out.println(p.name + " is elder than 18."));

因為filter()、forEach()的參數均為函數接口,所以可以替換為Lambda表達式的方式。簡單來理解,Lambda表達式就是允許開發者將代碼邏輯作為參數進行傳遞,關于Lambda表達式的詳細內容,請參Java 8的官方文檔。

聚合操作的使用

聚合操作是Java 8針對集合類,使編程更為便利的方式,可以與Lambda表達式一起使用,達到更加簡潔的目的。

前面例子中,對聚合操作的使用可以歸結為3個部分:

  1. 數據源部分:通過stream()方法,取得集合對象的數據集。
  2. 通過一系列中間(Intermediate)方法,對數據集進行過濾、檢索等數據集的再次處理。如上例中,使用filter()方法來對數據集進行過濾。
  3. 通過最終(terminal)方法完成對數據集中元素的處理。如上例中,使用forEach()完成對過濾后元素的打印。

中間方法除了filter()外,還有distinct()、sorted()、map()等等,其一般是對數據集的整理(過濾、排序、匹配、抽取等等),返回值一般也是數據集。

最終方法往往是完成對數據集中數據的處理,如forEach(),還有allMatch()、anyMatch()、findAny()、 findFirst(),數值計算類的方法有sum、max、min、average等等。最終方法也可以是對集合的處理,如reduce()、 collect()等等。reduce()方法的處理方式一般是每次都產生新的數據集,而collect()方法是在原數據集的基礎上進行更新,過程中不 產生新的數據集。

從上面的例子中可以看出,通過stream()方法,從集合對象獲取的數據集與集合對象的迭代器(Iterator)有些類似,但他們也不完全相同:

  1. 迭代器提供next()、hasNext()等方法,開發者可以自行控制對元素的處理,以及處理方式,但是只能順序處理;
  2. stream()方法返回的數據集無next()等方法,開發者無法控制對元素的迭代,迭代方式是系統內部實現的,同時系統內的迭代也不一定是順序的,還可以并行,如parallelStream()方法。并行的方式在一些情況下,可以大幅提升處理的效率。

除上述介紹的聚合操作外,Java 8中還提供了其他更為豐富的聚合操作,讀者可以參考Java 8的開發參考,了解更多內容。

總結

Java 8提供的聚合操作,以及一起使用的Lambda表達式為開發者帶來了便利,尤其在面向邏輯易變、開發迭代較快的項目應用時。但筆者個人認為,在帶來方便的 同時,可能也帶來了一些麻煩,如相同邏輯的復用,以及代碼的查錯、修改等,當然這些問題也是相對而言的。畢竟,任何事物都有兩面性,技術在不斷的發 展,Java也在不斷地調整自己的適應性,變得功能越來越多,越來越強大了。

 

來源:InfoQ - 趙永

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!