1. 程式人生 > >初探Lambda表示式-Java多核程式設計【0】從外部迭代到內部迭代

初探Lambda表示式-Java多核程式設計【0】從外部迭代到內部迭代

開篇

放假前從學校圖書館中借來一本書,Oracle官方的《精通Lambda表示式:Java多核程式設計》。
假期已過大半才想起來還沒翻上幾頁,在此先推薦給大家。
此書內容簡潔幹練,如果你對Java語法有基礎的認識看起來就會不費勁,唯一的缺點就是程式碼部分的內容以及排版有些錯誤,不過瑕不掩瑜,無傷大雅。
這個系列就是我對書中每一小節的一個提煉、總結以及實踐,每一個知識點我都會附上自己寫的程式碼,這些程式碼用來驗證所學的知識。
才疏學淺,如果有理解錯誤之處請指正,歡迎交流討論。

遍歷一個集合

最傳統的方法大概是用Iterator,當然我比較Low,習慣用i<arr.size()這類的迴圈。(現在我用for/in,本質上還是Iterator...)
這一類方法叫做外部迭代,意為顯式地進行迭代操作,即集合中的元素訪問是由一個處於集合外部的東西來控制的,在這裡控制著迴圈的東西就是迭代器。
書中舉的例子是pointList,我在這裡把它換成一個電話簿。

public class ContactList extends ArrayList<String>{}

裡面儲存著String型別的聯絡人。

for (Iterator<String> contactListIterator = contactList.iterator(); contactListIterator.hasNext(); ) {
    System.out.println(contactListIterator.next());
}

現在我們將這種遍歷方式換成內部迭代。
顧名思義,這種方式的遍歷將在集合內部進行,我們不會顯式地去控制這個迴圈。
無需關心遍歷元素的順序,我們只需要定義對其中每一個元素進行什麼樣的操作。注意在這種設定下可能無法直接獲取到當前元素的下標。
Java5中引入Collection.forEach(...)

,對集合中每一個元素應用其行為引數,這個方法是從其父介面Iterable中繼承。
現在我們可以去重寫forEach方法。

@Override
public void forEach() {
    for(String s : this) {
        System.out.println(s + " is your contact.");
    }
}

這下我們對電話簿呼叫forEach方法的時候,遍歷操作就會在類的內部完成了。
當然這看起來非常傻並且很不靈活。如果我們想把行為作為引數傳給forEach呢?
所幸Java8提供了這種可能,這種行為引數叫做Consumer。

public
interface Consumer<T> { void accept(T t); }

一個明顯的函式式介面,行為定義在accept方法中。
在定義好了我們自己的Consumer之後,現在這樣寫:

@Override
public void forEach(Consumer<String> c) {
    for(String s : this) {
        c.accept(s);
    }
}

傻的程度減輕了一些,當然還是不夠機智。
在這種情況下一個匿名內部類就能搞定問題(Android裡面監聽器寫到手抽...)

contactList.forEach(new Consumer<String>() {
    @Override
    public void accept(String s) {
        System.out.println(s + " is your contact.");
    }
});

這下足夠簡潔,當然聰明的編譯器應該能夠推斷出我們傳入forEach方法的只能是一個Consumer並且需要呼叫的就是Consumer的唯一方法accept,這段程式碼還有簡化的餘地。
所以現在需要登場的就是Lambda表示式了。
既然我們的電話簿ContactList本質上是一個ArrayList<String>,那麼編譯器也一定能推斷出Consumer的型別引數標識為String。
所以這下連引數型別都省了。

contactList.forEach(s -> System.out.println(s + " is your contact, again!"));

->前的s為引數名(即String s中的s),->後為方法體,也可以用一個大括號括起來,因為我這裡只寫了一句所以就沒用。
這就是內部迭代和Lambda相結合的終極奧義了。

現在附上測試程式碼:

import java.util.ArrayList;
import java.util.Iterator;
import java.util.function.Consumer;

public class ContactList extends ArrayList<String> {
    @Override
    public void forEach(Consumer<? super String> action) {
        super.forEach(action);
    }

    public static void main(String[] args) {
        ContactList contactList = new ContactList();
        contactList.add("Foo");
        contactList.add("Bar");
        contactList.add("Nico");

        for (Iterator<String> contactListIterator = contactList.iterator(); contactListIterator.hasNext(); ) {
            System.out.println(contactListIterator.next());
        }

        System.out.println("\n--- Consumer is coming! ---\n");

        contactList.forEach(new ContactAction());
        System.out.println("\n--- Lambda is coming! ---\n");
        contactList.forEach(s -> System.out.println(s + " is your contact, again!"));
    }

    static class ContactAction implements Consumer<String> {
        @Override
        public void accept(String s) {
            System.out.println(s + " is your contact.");
        }
    }
}

以及執行結果:

Foo
Bar
Nico

--- Consumer is coming! ---

Foo is your contact.
Bar is your contact.
Nico is your contact.

--- Lambda is coming! ---

Foo is your contact, again!
Bar is your contact, again!
Nico is your contact, again!

此外我們再進入forEach方法一看究竟:

@Override
public void forEach(Consumer<? super E> action) {
    Objects.requireNonNull(action);
    final int expectedModCount = modCount;
    @SuppressWarnings("unchecked")
    final E[] elementData = (E[]) this.elementData;
    final int size = this.size;
    for (int i=0; modCount == expectedModCount && i < size; i++) {
        action.accept(elementData[i]);
    }
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
}

可以發現其內部依舊是使用了一個for迴圈遍歷本身,只不過對併發做了一些處理而已。
可見外部迭代與內部迭代並沒有本質上的區別,兩者存在形式上的不同。
內部迭代的更多優勢與特性隨著本書的深入將會逐漸顯現。