for
起因
記得大學上C語言的課,第一次遇到的問題就是迴圈結構裡面的 for
。
選擇結構的 if
非常易懂,和日常生活的判斷沒有區別。
迴圈結構的 while
同樣比較好理解。
本質上是一個判斷
- 如果為真,繼續迴圈。
- 如果不假,則退出迴圈。
而 for
會稍微複雜一些。
for (init-expr; test-expr; update-expr)
body-statement
- 初始化表示式只執行一次
- 判斷表示式執行
- 判斷為真執行迴圈體
- 判斷為假退出迴圈體
- 執行更新表示式
在實際的語義等同於(唯一區別是init-expr一個是內部變數,一個是外部變數)
init-expr
while (test-expr) {
body-statement
update-expr
}
那麼我們為什麼要設計一個不那麼好理解的迴圈結構呢?
因為這時候才入了程式設計的門————抽象,以及約定。
如果我們再往底層挖,會發現在組合語言中是不存在while
、for
關鍵字的。
最開始的程式總是從左到右,從上到下一條路走到黑的。
後面程式設計人員意識到編寫重複的程式碼過於麻煩才創造了 loop
。
所以最開始需要人工寫一個for或者while迴圈。
while
好理解在於和自然語言(英語)完全符合。
當 條件滿足 時, {
執行 流程;
}
而 for
迴圈的好處在於規範了 while
的使用。
- 初始化語句(init-expr)一般只用於迴圈,所以放在內部,便於回收變數。
- 迴圈條件(test-expr)一般配合更新語句一起使用(update-expr),實現迴圈有限次數。
- 三者的拆分使編寫大段的迴圈或者巢狀迴圈時,更易讀。
// 傳統while迴圈
int i = 0;
while (i < 10) {
handleX();
i++;
int j = 0;
while (j < 5) {
handleY();
j++;
}
}
// for迴圈
for (int i = 0; i < 10; i++) {
handleX();
for (int j = 0; j < 5; j++) {
handleY();
}
}
所以 for 迴圈的出現也意味著程式設計人員開始在意的不僅僅是功能,而且看重可讀性。
然而這並不會被滿足。
之後還出現了
- 增強
for
,部分語言的for-each,for...in
。 lamdba
表示式中的forEach()
方法。
注意:以下按 Java 實現的 foreach 舉例。(其他程式語言不太熟悉)
增強 for
foreach
的規則
- 所有使用
foreach
的集合都必須實現Iterable
介面 - 透過
iterator()
獲取iterator
物件 - 透過
iterator.hasNext()
判斷是否存在元素。 - 透過
iterator.next()
獲取下一個元素。 - 透過
iterator.remove()
移除返回的元素。(可選)
增強 for
的語法
List<String> list = Arrays.asList("1", "2", "3", "4", "5");
// for 版本
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
// foreach 版本
for (String e : list) {
System.out.println(e);
}
// 去"糖"後的while版本
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
可以看出,foreach
對於 for、while
來說,好處是更加簡單,符合直覺。
- 無需判斷集合個數和中間變數減少程式碼出錯可能性,統一透過
hasNext()
處理。 - 不用分析每個集合類如何獲取元素,統一透過
next()
處理。 - 透過語法糖隱藏了
hasNext(), next()
邏輯,程式碼更易讀。
forEach()
Java 7/8 受到了函式式語言的影響,實現了更簡練的寫法。
// Iterable<T> 內實現的forEach
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
List<String> list = Arrays.asList("1", "2", "3", "4", "5");
// forEach()
list.forEach(i -> System.out.print(i));
// 方法引用
list.forEach(System.out::print);
可以看出,forEach()的好處顯而易見。
- 程式碼量比
foreach
更少,只關注遍歷元素,甚至連元素型別都可以省略。 - 使用了方法引用後更進一步,我們關注的是這個集合執行了哪些操作,遍歷每一次的含義在forEach()的方法已經體現了,甚至不需要寫遍歷的元素。
總結
可以看出,程式設計人員一直追尋的是更簡單,更易讀的程式碼。
- 他們不滿足於組合語言一遍遍的寫同一行程式碼,創造了 while
- 不滿足於 複雜或多層 while 的不可讀, 創造了 for
- 不滿足於 for 迴圈每一次定義的中間變數,創造了 foreach
- 不滿足於 foreach 需要迴圈每一次的元素,利用了lamdba 的 Consumer, 去掉了元素。