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個部分:
數據源部分:通過stream()方法,取得集合對象的數據集。
通過一系列中間(Intermediate)方法,對數據集進行過濾、檢索等數據集的再次處理。如上例中,使用filter()方法來對數據集進行過濾。
通過最終(terminal)方法完成對數據集中元素的處理。如上例中,使用forEach()完成對過濾後元素的打印。
中間方法除了filter()外,還有distinct()、sorted()、map()等等,其一般是對數據集的整理(過濾、排序、匹配、抽取等等),返回值一般也是數據集。
最終方法往往是完成對數據集中數據的處理,如forEach(),還有allMatch()、anyMatch()、findAny()、findFirst(),數值計算類的方法有sum、max、min、average等等。最終方法也可以是對集合的處理,如reduce()、collect()等等。reduce()方法的處理方式一般是每次都產生新的數據集,而collect()方法是在原數據集的基礎上進行更新,過程中不產生新的數據集。
從上面的例子中可以看出,通過stream()方法,從集合對象獲取的數據集與集合對象的迭代器(Iterator)有些類似,但他們也不完全相同:
迭代器提供next()、hasNext()等方法,開發者可以自行控制對元素的處理,以及處理方式,但是只能順序處理;
stream()方法返回的數據集無next()等方法,開發者無法控制對元素的迭代,迭代方式是系統內部實現的,同時系統內的迭代也不一定是順序的,還可以並行,如parallelStream()方法。並行的方式在一些情況下,可以大幅提升處理的效率。
除上述介紹的聚合操作外,Java 8中還提供了其他更為豐富的聚合操作,讀者可以參考Java 8的開發參考,了解更多內容。
Java 8提供的聚合操作,以及一起使用的Lambda表達式為開發者帶來了便利,尤其在面向邏輯易變、開發迭代較快的項目應用時。但筆者個人認為,在帶來方便的同時,可能也帶來了一些麻煩,如相同邏輯的復用,以及代碼的查錯、修改等,當然這些問題也是相對而言的。畢竟,任何事物都有兩面性,技術在不斷的發展,Java也在不斷地調整自己的適應性,變得功能越來越多,越來越強大了。