Java 8新特性探究(三)解開lambda最強做用的神祕面紗

咱們期待了好久lambda爲java帶來閉包的概念,可是若是咱們不在集合中使用它的話,就損失了很大價值。現有接口遷移成爲lambda風格的問題已經經過default methods解決了,在這篇文章將深刻解析Java集合裏面的批量數據操做(bulk operation),解開lambda最強做用的神祕面紗。 html

1.關於JSR335

JSR是Java Specification Requests的縮寫,意思是Java 規範請求,Java 8 版本的主要改進是 Lambda 項目(JSR 335),其目的是使 Java 更易於爲多核處理器編寫代碼。JSR 335=lambda表達式+接口改進(默認方法)+批量數據操做。加上前面兩篇,咱們已經是完整的學習了JSR335的相關內容了。 java

2.外部VS內部迭代

之前Java集合是不可以表達內部迭代的,而只提供了一種外部迭代的方式,也就是for或者while循環。
編程

List persons = asList(new Person("Joe"), new Person("Jim"), new Person("John"));
for (Person p :  persons) {
   p.setLastName("Doe");
}
上面的例子是咱們之前的作法,也就是所謂的外部迭代,循環是固定的順序循環。在如今多核的時代,若是咱們想並行循環,不得不修改以上代碼。效率能有多大提高還說定,且會帶來必定的風險(線程安全問題等等)。
要描述內部迭代,咱們須要用到Lambda這樣的類庫,下面利用lambda和Collection.forEach重寫上面的循環
persons.forEach(p->p.setLastName("Doe"));
如今是由jdk 庫來控制循環了,咱們不須要關心last name是怎麼被設置到每個person對象裏面去的,庫能夠根據運行環境來決定怎麼作,並行,亂序或者懶加載方式。這就是內部迭代,客戶端將行爲p.setLastName當作數據傳入api裏面。

內部迭代其實和集合的批量操做並無密切的聯繫,藉助它咱們感覺到語法表達上的變化。真正有意思的和批量操做相關的是新的流(stream)API。新的java.util.stream包已經添加進JDK 8了。 api

3.Stream API

流(Stream)僅僅表明着數據流,並無數據結構,因此他遍歷完一次以後便再也沒法遍歷(這點在編程時候須要注意,不像Collection,遍歷多少次裏面都還有數據),它的來源能夠是Collection、array、io等等。 數組

3.1中間與終點方法

流做用是提供了一種操做大數據接口,讓數據操做更容易和更快。它具備過濾、映射以及減小遍歷數等方法,這些方法分兩種:中間方法和終端方法,「流」抽象天生就該是持續的,中間方法永遠返回的是Stream,所以若是咱們要獲取最終結果的話,必須使用終點操做才能收集流產生的最終結果。區分這兩個方法是看他的返回值,若是是Stream則是中間方法,不然是終點方法。具體請參照Stream的api

簡單介紹下幾個中間方法(filter、map)以及終點方法(collect、sum)
安全

3.1.1Filter

在數據流中實現過濾功能是首先咱們能夠想到的最天然的操做了。Stream接口暴露了一個filter方法,它能夠接受表示操做的Predicate實現來使用定義了過濾條件的lambda表達式。
數據結構

List persons = …
Stream personsOver18 = persons.stream().filter(p -> p.getAge() > 18);//過濾18歲以上的人

3.1.2Map

假使咱們如今過濾了一些數據,好比轉換對象的時候。Map操做容許咱們執行一個Function的實現(Function<T,R>的泛型T,R分別表示執行輸入和執行結果),它接受入參並返回。首先,讓咱們來看看怎樣以匿名內部類的方式來描述它:
閉包

Stream adult= persons
              .stream()
              .filter(p -> p.getAge() > 18)
              .map(new Function() {
                  @Override
                  public Adult apply(Person person) {
                     return new Adult(person);//將大於18歲的人轉爲成年人
                  }
              });
如今,把上述例子轉換成使用lambda表達式的寫法:
Stream map = persons.stream()
                    .filter(p -> p.getAge() > 18)
                    .map(person -> new Adult(person));

3.1.3Count

count方法是一個流的終點方法,可以使流的結果最終統計,返回int,好比咱們計算一下知足18歲的總人數
app

int countOfAdult=persons.stream()
                       .filter(p -> p.getAge() > 18)
                       .map(person -> new Adult(person))
                       .count();

3.1.4Collect

collect方法也是一個流的終點方法,可收集最終的結果 框架

List adultList= persons.stream()
                       .filter(p -> p.getAge() > 18)
                       .map(person -> new Adult(person))
                       .collect(Collectors.toList());
或者,若是咱們想使用特定的實現類來收集結果:
List adultList = persons
                 .stream()
                 .filter(p -> p.getAge() > 18)
                 .map(person -> new Adult(person))
                 .collect(Collectors.toCollection(ArrayList::new));

篇幅有限,其餘的中間方法和終點方法就不一一介紹了,看了上面幾個例子,你們明白這兩種方法的區別便可,後面可根據需求來決定使用。

3.2順序流與並行流

每一個Stream都有兩種模式:順序執行和並行執行。
順序流:

List <Person> people = list.getStream.collect(Collectors.toList());
並行流:
List <Person> people = list.getStream.parallel().collect(Collectors.toList());
顧名思義,當使用順序方式去遍歷時,每一個item讀完後再讀下一個item。而使用並行去遍歷時,數組會被分紅多個段,其中每個都在不一樣的線程中處理,而後將結果一塊兒輸出。

3.2.1並行流原理:

List originalList = someData;
split1 = originalList(0, mid);//將數據分小部分
split2 = originalList(mid,end);
new Runnable(split1.process());//小部分執行操做
new Runnable(split2.process());
List revisedList = split1 + split2;//將結果合併
你們對hadoop有稍微瞭解就知道,裏面的 MapReduce  自己就是用於並行處理大數據集的軟件框架,其 處理大數據的核心思想就是大而化小,分配到不一樣機器去運行map,最終經過reduce將全部機器的結果結合起來獲得一個最終結果,與MapReduce不一樣,Stream則是利用多核技術可將大數據經過多核並行處理,而MapReduce則能夠分佈式的。

3.2.2順序與並行性能測試對比

若是是多核機器,理論上並行流則會比順序流快上一倍,下面是測試代碼
long t0 = System.nanoTime();

        //初始化一個範圍100萬整數流,求能被2整除的數字,toArray()是終點方法

        int a[]=IntStream.range(0, 1_000_000).filter(p -> p % 2==0).toArray();

        long t1 = System.nanoTime();

        //和上面功能同樣,這裏是用並行流來計算

        int b[]=IntStream.range(0, 1_000_000).parallel().filter(p -> p % 2==0).toArray();

        long t2 = System.nanoTime();

        //我本機的結果是serial: 0.06s, parallel 0.02s,證實並行流確實比順序流快

        System.out.printf("serial: %.2fs, parallel %.2fs%n", (t1 - t0) * 1e-9, (t2 - t1) * 1e-9);

3.3關於Folk/Join框架

應用硬件的並行性在java 7就有了,那就是 java.util.concurrent 包的新增功能之一是一個 fork-join 風格的並行分解框架,一樣也很強大高效,有興趣的同窗去研究,這裏不詳談了,相比Stream.parallel()這種方式,我更傾向於後者。

4.總結

若是沒有lambda,Stream用起來至關彆扭,他會產生大量的匿名內部類,好比上面的3.1.2map例子,若是沒有default method,集合框架更改勢必會引發大量的改動,因此lambda+default method使得jdk庫更增強大,以及靈活,Stream以及集合框架的改進即是最好的證實。

java 8特性探究系列寫了3篇了,做爲大餐,將java 8的重量級特性lambda與default method寫在前面,下篇上個小菜,葷素搭配,也是語言相關的,JEP104 Java 類型的註解的探究,同時謝謝你們的支持,歡迎提出建議。若是你想了解哪些特性,歡迎給我發留言。

轉載時候請註明出處。 http://my.oschina.net/benhaile 

相關文章
相關標籤/搜索