初探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(...)
現在我們可以去重寫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迴圈遍歷本身,只不過對併發做了一些處理而已。
可見外部迭代與內部迭代並沒有本質上的區別,兩者存在形式上的不同。
內部迭代的更多優勢與特性隨著本書的深入將會逐漸顯現。