【Scala筆記——道】Scala 循環遍歷 for詳解

Scala for循環

基本使用

加強型for循環

scala基本for循環以下,代碼將names遍歷並打印包含的名字。html

val names = Seq("Kitty", "Tom", "Luke", "Kit")


for (name <- names) {

  println(name)
}

相較Java,語法層面來看只是將 :換成<-。實際上因爲Scala已經將:用於類型定義,這裏使用:會形成二義性,scala這裏使用<-用於循環語義。java

生成器表達式

在Java中循環常常會用到數值遞增/遞減,例如for(int i = 0, i < 10, i++)編程

scala中不提供相似的語法結構,與之對應的是提供了 生成器表達式(Generator Expression),之因此叫這個名字,是由於該表達式會基於集合生成單獨的數值。左箭頭操做符(<-) 用於對像列表這樣的集合進行遍歷。api

for (i <- 1 to 10) println(i)

不一樣於Java循環中數值操做,Scala取而代之的是提供了Range類型
持 Range 的 類 型 包 括 Int 、 Long 、 Float 、 Double 、 Char 、BigInt和 BigDecimal安全

具體示例以下app

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循環oop

java中對於加強型for循環必須實現java.util.Iterable接口,事實上經常使用的Java集合類都已經實現了Iterable接口。spa

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詳解

相關文章
相關標籤/搜索