一、前言
前面学习了Scala的Numbers,接着学习Scala的Control Structures(控制结构)。
二、Control Structures
Scala中的控制结构与Java中的颇为类似,但也有所不同,例如,if/then/else控制结构与Java的类似,但是其可以返回值,虽然Java中有三元运算符的特殊语法,但是在Scala中使用if就可以达到同样的效果。
val x = if (a) y else z
同样,Scala的try/catch/finally 结构与Java的也类似,但是Scala在catch中时使用的模式匹配。
当使用for循环时,情况会变得更有趣,for循环的基本用法在Scala和Java中都相同,但是在Scala可以添加守卫。例如,可以使用两个for循环来读取文件中的每一行,并且对每一行的每个字符进行操作。
for (line <- source.getLines) { for { char <- line if char.isLetter } // char algorithm here ... }
在Scala中,还可以使用更精炼的语句实现上述功能
for { line <- source.getLines char <- line if char.isLetter } // char algorithm here ...
在Scala中,使用for循环可以让你能够很轻易的将一个集合转化成另外一个集合
在最基础的使用中,Scala的match表达式与Java的Switch语句类似,你可以匹配任何对象,并且从对象中解析信息。
2.1 for和foreach循环
1. 问题描述
你需要遍历集合的元素,或者操作集合中的元素,或者在已有集合上创建新的集合
2. 解决方案
有很多方式可以遍历集合,如for、while循环,还有集合的foreach、map、flatMap方法等,首先看看for循环
当你的算法有多行时,也可以使用for循环,如下所示
上述示例中仅仅只是遍历了集合中的元素,并且没有返回值,如新的Array,当你想要从已有集合中创建新的集合时,可以使用for/yield
for/yield结构返回了一个新的Array。
当在for循环中需要访问计数器时,可以使用如下方法
Scala集合也提供了zipWithIndex方法来创建循环计数器
下面示例展示了如何通过Range来执行循环三次
如下示例展示了如何使用守卫(在for循环中使用if语句)
可以使用如下精炼方式遍历Map的键集合和值集合
3. 讨论
当你在集合上使用for/yield时,会得到一个新的集合,而仅仅使用for时,则不会产生新的集合,仅仅只是利用for的副作用对集合元素进行操作。for循环可能不是遍历集合最好的方式,使用foreach、map、flatMap、collect、reduce等方法可能会更高效的解决问题。
你可以调用foreach方法来遍历每个集合元素
可以使用匿名函数对元素进行操作
for循环如何被翻译
· for循环会被编译器翻译成foreach方法
· 有守卫的for循环会被翻译成withFilter方法(在foreach之后)
· 有yield的for循环会被翻译成map方法
· 有yield和守卫的for循环会被翻译成withFilter(在map之后)
为了验证for循环的翻译过程,新建Main.scala文件,源代码如
class Main { for (i <- 1 to 10) println(i) }
使用scalac -Xprint:parse Main.scala命令进行编译
可以看到for循环被翻译成了foreach方法
其他的带守卫、yield的for循环也可以采用上述方法进行查看,不再累赘
2.2 使用多计数器的for循环
1. 问题描述
你想要创建拥有多个计数器的循环,如当遍历一个多维数组
2. 解决方案
你可以使用如下方法创建多个计数器
一种更好的方式是使用中括号
同理,也可以使用三个计数器
当遍历多位数组时,该方法很有效
3. 讨论
在循环中,使用<-符号生成Range被称为生成器,可以在一个循环中使用多个生成器
2.3 使用内嵌语句的for循环
1. 问题描述
你想要在for循环中添加一个或多个条件子句,通常在筛选集合中的某些元素
2. 解决方案
可以在生成器后面添加if语句
或者使用中括号
上述的if语句被称为过滤器或守卫,下面示例添加了多个守卫
3. 讨论
在for循环中使用守卫可以是代码精炼并且提高可读性,当然也可以使用传统的方法
2.4 创建for/yield组合
1. 问题描述
你想要通过对原始集合中的每个元素应用某种算法(一个或多个守卫)来从现有集合创建新的集合
2. 解决方案
使用yield和算法来从现有集合生成新的集合
如果算法有多行,可以采用如下形式
通常情况下,使用yield新生的集合的类型和原始集合的类型相同,如原始集合时ArrayBuffer类型,则新生的集合也是ArrayBuffer类型
3. 讨论
当在for循环中使用yield时,可以将其看做如下
· 开始运行时,for/yield会新建一个空的集合,类型和原始集合类型相同
· 在for循环的每次迭代中,从输入集合的当前元素创建新的输出元素,放置在新的集合中
· 当for循环结束时,返回新的集合中的所有内容
没有守卫的for/yield语句就相当于调用集合的map方法
2.5 实现break和continue
1. 问题描述
你需要使用break和continue,但是在Scala中并没有break和continue关键字
2. 解决方案
scala.util.control.Breaks提供了break功能
package com.hust.grid.leesf.breakcontinue /** * Created by LEESF on 2017/1/18. */ import util.control.Breaks._ object BreakAndContinueDemo extends App{ println("\n=== BREAK EXAMPLE ===") breakable { for (i <- 1 to 10) { println(i) if (i > 4) break // break out of the for loop } } println("\n=== CONTINUE EXAMPLE ===") val searchMe = "peter piper picked a peck of pickled peppers" var numPs = 0 for (i <- 0 until searchMe.length) { breakable { if (searchMe.charAt(i) != 'p') { break // break out of the 'breakable', continue the outside loop } else { numPs += 1 } } } println("Found " + numPs + " p's in the string.") }
结果
=== BREAK EXAMPLE === 1 2 3 4 5 === CONTINUE EXAMPLE === Found 9 p's in the string.
值得注意的是break和breakable都不是Scala的关键字,它们是scala.util.control.Breaks包下的方法
private val breakException = new BreakControl def break(): Nothing = { throw breakException } def breakable(op: => Unit) { try { op } catch { case ex: BreakControl => if (ex ne breakException) throw ex } }
可以看到在调用break方法时,会抛出一个异常,此时breakable会捕捉到此异常,利用这种方式实现了break和continue的功能
3. 讨论
如果你不喜欢使用break和continue时,可以使用如下方法,如可以使用带有守卫的for循环
var barrelIsFull = false for (monkey <- monkeyCollection if !barrelIsFull) { addMonkeyToBarrel(monkey) barrelIsFull = checkIfBarrelIsFull }
或者使用return直接返回
def sumToMax(arr: Array[Int], limit: Int): Int = { var sum = 0 for (i <- arr) { sum += i if (sum > limit) return limit } sum } val a = Array.range(0,10) println(sumToMax(a, 10))
2.6 使用类似三元运算符的if表达式
1. 问题描述
你需要使用if表达式来像三元运算符那样简洁的解决问题
2. 解决方案
在Scala中没有三元运算法,但是可以使用if/else表达式
3. 讨论
在Java中可以使用?:三元运算符,在Scala可以使用if/else表达式
2.7 使用类似switch语句的match表达式
1. 问题描述
你想要创建向Java那样简单的switch语句,如星期几,哪一月
2. 解决方案
使用Scala的match表达式
// i is an integer i match { case 1 => println("January") case 2 => println("February") case 3 => println("March") case 4 => println("April") case 5 => println("May") case 6 => println("June") case 7 => println("July") case 8 => println("August") case 9 => println("September") case 10 => println("October") case 11 => println("November") case 12 => println("December") // catch the default with a variable so you can print it case whoa => println("Unexpected case: " + whoa.toString) }
还可以直接利用match表达式的返回值
val month = i match { case 1 => "January" case 2 => "February" case 3 => "March" case 4 => "April" case 5 => "May" case 6 => "June" case 7 => "July" case 8 => "August" case 9 => "September" case 10 => "October" case 11 => "November" case 12 => "December" case _ => "Invalid month" // the default, catch-all }
当写match表达式时,推荐使用@switch注解,当switch不能编译成一个tableswitch或lookupswitch,该注解会提供编译时警告,将switch编译成tableswitch或lookupswitch会有更高的性能,因为结果是在分支表而不是一个决策树里,当给表达式赋值时,它可以直接跳转到结果,而不是遍历决策树
import scala.annotation.switch class SwitchDemo { val i = 1 val x = (i: @switch) match { case 1 => "One" case 2 => "Two" case _ => "Other" } }
先使用scalac SwitchDemo命令进行编译,然后使用javap SwitchDemo命令反编译
可以看到生成了tableswitch表,当修改代码如下时
import scala.annotation.switch class SwitchDemo { val i = 1 val Two = 2 // added val x = (i: @switch) match { case 1 => "One" case Two => "Two" // replaced the '2' case _ => "Other" } }
同样使用scalac SwitchDemo命令进行编译
这表明match表达式无法生成tableswitch或lookupswitch,当使用javap命令反汇编时,也找不到tableswitch
3. 讨论
match表达式非常灵活,可以匹配不同的类型
def getClassAsString(x: Any): String = x match { case s: String => s + " is a String" case i: Int => "Int" case f: Float => "Float" case l: List[_] => "List" case p: Person => "Person" case _ => "Unknown" }
可以使用case _通配符来匹配之前的所有case无法匹配的情况,或者使用case default进行赋值
case default => println(default)