zoukankan      html  css  js  c++  java
  • 【Scala笔记——道】Scala 循环遍历 for详解

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

  • 相关阅读:
    还原网站上被压缩的JS代码方便阅读
    让chrome浏览器变成在线编辑器
    awk之NF的妙用
    Xargs用法详解
    CU论坛常用知识点汇总
    awk中RS,ORS,FS,OFS区别与联系
    SHELL十三问[转载自CU论坛]
    关于shell中常见功能的实现方式总结
    shell实现ftp命令示例
    MySQL基础
  • 原文地址:https://www.cnblogs.com/cunchen/p/9464092.html
Copyright © 2011-2022 走看看