Scala for循環
基本使用
增強型for循環
scala基本for循環如下,代碼將names遍歷並打印包含的名字。
val names = Seq("Kitty", "Tom", "Luke", "Kit")
for (name <- names) {
println(name)
}
相較Java,語法層面來看只是將 :
換成<-
。實際上由於Scala已經將:
用於類型定義,這里使用:
會造成二義性,scala這里使用<-
用於循環語義。
生成器表達式
在Java中循環經常會用到數值遞增/遞減,例如for(int i = 0, i < 10, i++)
scala中不提供類似的語法結構,與之對應的是提供了 生成器表達式(Generator Expression),之所以叫這個名字,是因為該表達式會基於集合生成單獨的數值。左箭頭操作符(<-) 用於對像列表這樣的集合進行遍歷。
for (i <- 1 to 10) println(i)
不同於Java循環中數值操作,Scala取而代之的是提供了Range類型
持 Range 的 類 型 包 括 Int 、 Long 、 Float 、 Double 、 Char 、BigInt和 BigDecimal
具體示例如下
1 to 10 // Int類型的Range,包括區間上限,步長為1 (從1到10)
1 until 10 // Int類型的Range,不包括區間上限,步長為1 (從1到9)
1 to 10 by 3 // Int類型的Range,包括區間上限,步長為3
10 to 1 by -3 // Int類型的遞減Range,包括區間下限,步長為-3
1.1f to 10.3f by 3.1f // Float類型的Range,步長可以不等於1
保護式
如何在遍歷中更細粒度控制遍歷呢,scala提供了保護式(Guard),具體實現如下
val names = Seq("Kitty", "Tom", "Luke", "Kit")
for (name <- names
if name.startsWith("K") //以K開頭
if name.endsWith("t") //以t結尾
) {
println(name)
}
輸出如下:
Kit
原理探尋
java中jdk1.5版本以后通過 迭代器實現了增強型for循環
java中對於增強型for循環必須實現java.util.Iterable接口,事實上常用的Java集合類都已經實現了Iterable接口。
public interface Iterable<T> {
Iterator<T> iterator();
}
public interface Iterator<E> {
boolean hasNext();
E next();
void remove()
}
實際上java中增強型for循環是通過Iterable接口,拿到具體的迭代器(Iterator)進行遍歷,相當於迭代器while循環的語法糖
Iterator it = list.iterator();
while(it.hasNext()) {
T t = it.next();
...
}
scala中的集合類並沒有通過接口去實現一個迭代器,而scala不可能憑空探測集合類的具體實現,那么在scala中對於容器的for循環遍歷是怎么實現的呢?
首先我們自己實現一個容器類,通過調用for循環看一下結果
class AbleForLoopA(name: String)
val s1 = new AbleForLoopA("a")
for (s <- s1) println
實際運行會出現以下錯誤
Error:(9, 19) value foreach is not a member of loop.LoopTest.AbleForLoopA
for (s <- s1) println
錯誤提示我們,需要一個foreach成員,參考集合類的foreach方法,實現代碼如下
class AbleForLoopB(name: String) {
def foreach[U](f: String => U) = if (!name.isEmpty) f(name)
}
val s2 = new AbleForLoopB("b")
for (s <- s2) println(s)
這時可以正確執行並打印結果,所以實際上scala只類型中包含foreach方法,就可以通過for循環進行調用。更進一步來看,上述代碼實際上相當於
s2.foreach(println)
我們對上述代碼反編譯結果如下
public void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC
Code:
stack=3, locals=3, args_size=2
0: new #12 // class loop/LoopTest$AbleForLoopB
3: dup
4: ldc #27 // String b
6: invokespecial #30 // Method loop/LoopTest$AbleForLoopB."<init>":(Ljava/lang/String;)V
9: astore_2
10: aload_2
11: invokedynamic #53, 0 // InvokeDynamic #0:apply:()Lscala/Function1;
16: invokevirtual #57 // Method loop/LoopTest$AbleForLoopB.foreach:(Lscala/Function1;)Ljava/lang/Object;
19: pop
20: return
public static final void $anonfun$main$1(java.lang.String);
descriptor: (Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #68 // Field scala/Predef$.MODULE$:Lscala/Predef$;
3: aload_0
4: invokevirtual #72 // Method scala/Predef$.println:(Ljava/lang/Object;)V
7: return
反編譯結果也證實了我們的猜想,實際上scala中的for循環實際上是對於foreach的語法糖。scala沒有通過接口進行統一約束foreach,而是通過動態代理直接調用foreach方法。
從本質上來看for(li: list) 和 list.foreach 兩種形式的for循環並無本質上的不同。而由於前者通過動態代理實現,因此實際上直接使用foreach能有更好的效率。
實際上scala更推薦使用 list.foreach形式的for循環。
再談應用
事實上,scala通過filter以及一些其他的條件循環語句來實現循環控制。例如需要對循環進行篩選。
val names = Seq("Kitty", "Tom", "Luke", "Kit")
//Method 1
for (name <- names
if name.startsWith("K") //允許在這里增加判斷語句,此處括號可以省略
//if name.endWith("t") //允許你添加多個判斷
) {
println(name)
}
//Method 2
names.filter(_.startsWith("K")).foreach(println) //通過fileter過濾
//Method1 和 Method2 執行結果是一樣的,結果如下
Kitty
Kit
scala for循環中並未提供 break、continue這種形式的控制語句。那么scala中的循環是通過什么實現循環控制呢?
val names = Seq("Kitty", "Tom", "Luke", "Kit")
println("----------------------")
names.takeWhile(!_.startsWith("L")).foreach(println) //返回一個迭代器,指代從it開始到第一個不滿足條件p的元素為止的片段。
//執行結果
//Kitty
//Tom
println("----------------------")
names.dropWhile(_.startsWith("K")).foreach(println) //返回一個新的迭代器,指向it所指元素中第一個不滿足條件p的元素開始直至終點的所有元素。
//執行結果
//Tom
//Luke
//Kit
實際上還可以對這種結果進行復合,例如
names.takeWhile(!_.startsWith("L")).filter(_.startsWith("K")).foreach(println)
//執行結果
//Kitty
在大多時候,我們不會使用foreach,因為foreach沒有返回值意味着副作用。實際上我們更多時候是使用map、flatMap。從函數式編程來說,輸入參數經過函數運算變為另外一種值,並且這個運算是可替代的。
大多數情況下map,flatMap已經可以滿足我們的需求,map和flatMap所進行的函數運算是棧封閉的運算,也就是說循環的前者並不會和后者的計算有關系。例如,需要將一個序列的數字進行求和,此時如果在map中引入外部變量,則破壞map的棧封閉從而破壞線程安全。如果需要有上下文影響的循環,此時就需要使用到 foldLeft、 foldRight。如需了解,請看
【Scala筆記——道】Scala List 遍歷 foldLeft / foldRight詳解