1.If语句
Scala的If语句可以完成其他语言中If语句,于此同时,if/else通常还有值,可以用来赋值,或者代替三元条件运算符(?:)。不过它可以比条件运算符更强大,因为你可以在if-else里面写很复杂的程序块。
1.1.普通的If语句
package com.tv189.foundation /** * Created by molyeo on 2015/7/30. */ object IfCondition { def main(args: Array[String]) { val checkPoint = 80 def check(current: Int): Unit = { if (current >= checkPoint) { println("Success") } else { println("Fail") } } check(80) check(90) check(30) } }
1.2.用If语句赋值
if/else语句能用来赋值,进而代替?:
运算符,得益于Scala的一个重要特点,在Scala中,每个语句块都有值,就是该语句块最后一个语句的值。请看下面的代码。
def printAbs(x: Int) { val abs = if(x < 0) -x else x println("The Abs of %d is %d".format(x, abs)) } def abs(x: Int) = if(x < 0) -x else x printAbs(-5) println(abs(-6))
第一个函数printAbs里,用了一个if-else语句给abs赋值,这个语句的作用就相当于C语言或者Java中的?:
操作符。 进一步的,由于语句块的最后一个语句的值,是整个块的值,语句块只有一个语句时,大括号{}可以省略。因此,求绝对值的函数可以简略至只有一行。
2.While语句
Scala的While语句与其他语言中While语句类似,有经验的程序员可以跳过这一小节。与If语句不同,While语句本身没有值。或者说,整个While语句的结果是Unit类型的()。
2.1.while循环
与大部分语言一样,Scala中的while循环包括循环体和循环条件两部分,循环条件满足时,循环体会一直执行下去。
package com.tv189.foundation /** * Created by molyeo on 2015/7/30. */ object WhileCondition extends App{ def reverse(number: Int) = { var result = 0; var temp = number; while (temp != 0) { result = 10 * result + temp % 10; temp /= 10; } result; } println("The result of reverse(12345) is "+reverse(12345)) def sum(input: Int) = { var total = 10; var temp = input; while (temp > 0) { total = temp + total; temp = temp - 1; } total; } println("The result of sum(10) is "+sum(10)); }
运行结果
The result of reverse(12345) is 54321 The result of sum(10) is 65
2.2.do-while循环
do-while循环与while循环的区别只在于,do-while循环是先执行循环体,然后再判断条件,这意味着循环体至少会被执行一次。
val i = 0 do { println("executed once") } while( i != 0)
请注意
Scala中,赋值语句没有值,不像C和Java那样,因此在别的语言常见的while((c = getchar()) != ‘\n‘)
,Scala里面不能这样写。
这是因为,在Scala中,c = getchar()
,虽然c会被赋值,但是该值不会赋给整个语句。这与其他语言不同,在C语言和Java中,c的值也会赋值给整个语句。
3.For语句
Scala的For语句跟其他语言差别很大,即使是有经验的程序员也需要仔细了解这部分内容。
在Scala中,没有形如for (initialize; test; update)
的for循环,要实现相关功能,要么用while代替,要么,用Scala式的for语句for (i <- 1 to n)
。
3.1.用于集合的迭代
3.1.1.基本语法
用于迭代一个集合的for语句,格式为for(item <- collection)
,这有点像Java里的for (type var : arr)
,或者C#里的foreach(type var in arr)
。
由于Scala里创建一个集合的方式很多,所以for语句相对来说比较灵活,请看下面的示例。
package com.tv189.foundation /** * Created by molyeo on 2015/7/30. */ object ForCondition extends App{ val list=List("one","two","three","four"); println("-------------use to-------------") for(i<-0 to list.length-1){ print(" "+list(i)); } println(); println("-------------use until-------------") for(i<-0 until list.length){ print(" "+list(i)); } println(); println("-------------use until-------------") for(item<-list){ print(" "+item) } }
请注意
请注意以上代码to和until的差别,to包含上边界,until则不包含。
3.1.2.嵌套for循环
Scala中,嵌套for循环比别的语言要容易,只要将表达式都放进for的括号里,用分号(;)隔开。比如:
for(i <- 0 to 1; j <- 1 to 2; k <- 2 to 3) { println("i = %d, j = %d, k = %d".format(i, j, k)) }
以上代码,是i,j,k三个循环的嵌套。
3.1.3.条件for循环
for循环的循环器还可以带有条件语句,从而筛选循环的项目。比如:
for{i <- 0 to 5 if i % 2 == 0 j <- 1 to 2} { println("i = %d, j = %d".format(i, j)) }
以上代码中,i带有条件 i % 2 == 0
,这意味着,仅有满足这个条件的i才会被循环执行。
3.2.中途绑定变量
请看下面的示例。
val list = List("Html", "XML", "JSON", "text", "md") for{ ext <- list lower = ext.toLowerCase if lower != "html"} { println("Accepted data format: " + lower) }
你可能已经发现了,上面代码中,toLowerCase在一个循环中可能会执行两次。这种情况我们希望能避免掉。 Scala的确也提供了这样的途径,你可以在for语句中途引入变量,然后在循环体内可以使用它。
上面代码中,我们引入了变量lower,这与通常我们定义变量的方式相比,只是少了val。这个变量在for表达式中和循环体中都可以使用。
3.3.用于产生新的集合
for语句,配合yield,可以用来产生新的集合。如下例所示。
val result = for(i <- 1 to 3; j <- 2 to 4) yield { i + j } println(result)
上面代码产生了一个集合Vector(3, 4, 5, 4, 5, 6, 5, 6, 7)。
请注意
for-yield的格式是 for(claus) yield {body},不能将yield放到大括号内。
4.基本Match语句
Scala的Match语句功能基本上类似于其他语言中的switch-case,不过要强大很多。这一小节我们只介绍基本语法,关于“强大”的部分,留待模式匹配介绍。
用过switch-case语句的程序员知道,它的作用是让你在一系列选项中选择一个分支执行。mach与之类似,不过还是稍有差别。
"your selector" match { case "case 1" => //handle case 1 case "case 2" => //handle case 2 ... case _ => //handle the rest, like default in switch-case }
除了上述语法上的差别外,match与switch语句,有如下的不同点。
- 任意类型的常量或结果为常量的表达式都可以用于match语句。许多语言的switch只允许基本类型,Java则更严格,只允许整形和枚举类型。
- 每个分支不需要以break结束,因为Scala里没有贯穿执行(fall through)。
- 与if表达式一样,match表达式也有结果。这也是可以理解的,match其实是多个分支的if的另一种写法。
我们看一个实际的例子。
def score(grade: Char) = { grade match { case ‘A‘ => println("85~100") case ‘B‘ => println("70~84") case ‘C‘ => println("60~69") case ‘D‘ => println("45~60") case ‘E‘ => println("<45") case _ => println("Input error") } } def passed_?(grade: Char) = { grade match { case ‘A‘ | ‘B‘ | ‘C‘ => true case _ => false } } score(‘B‘) if(passed_?(‘B‘)) println("Congrats!") else println("Uh~ oh, we are sorry...")
函数score包含一个常规的match语句,这里我们能看到,每个分支只执行本分支的代码,没有贯穿。最后有一个case _
处理未列出的情况。Scala中下划线(_)经常用作通配符。
函数passed_?有两处需要注意。一个是这个函数的返回类型是Boolean,由于该函数体只有一个match语句,显然其返回值就是match语句的值。match语句的值,其类型就是各个分支的共同类型。 如果这些类型不相同,就取最近的共同父类。
另一个值得注意的地方是,case ‘A‘ | ‘B‘ | ‘C‘ => true
这正是其他语言中,switch-case贯穿执行的情况,类似于
case ‘A‘ : case ‘B‘ : case ‘C‘ : return true;
这也从一个侧面说明了,Scala里面,取消break和fall-through,能让代码更简洁,语义更清晰,而且让程序员不容易犯无心之过。
5.异常处理
Scala的异常处理与其他语言是一样的,功能一样,语法也只有细微的差别。异常机制的出现是为了处理程序中不正常的情况,比如要查找的文件不存在,数据库连接不上,网络中断等等。 当碰到异常情况时,方法抛出一个异常,终止方法本身的执行,异常传递到其调用者,调用者可以处理该异常,也可以升级到它的调用者。运行系统会一直这样升级异常,直到有调用者能处理它。 如果一直没有处理,则终止整个程序。
5.1.抛出异常
Scala中异常的抛出与Java一样,用throw关键字,抛出一个异常对象。所有异常都是Throwable的子类型,这与Java里一样。事实上,Scala的Throwable就是java.lang.Throwable的别名。
正如在底层类型Nothing里介绍过的那样,throw表达式是有类型的,就是Nothing,因为Nothing是所有类型的子类型,所以throw表达式可以用在需要类型的地方。
def divide(x: Int, y: Int): Int = { if (y == 0) throw new Exception("Divide by zero") else x / y }
以上代码divide方法的返回类型是Int,由于throw new Exception("Divide by zero")
的类型是Nothing,是Int的子类,所以上述代码是正确的。
5.2.捕捉异常
异常捕捉的机制与其他语言中一样,如果有异常发生,catch字句是按次序捕捉的。因此,在catch字句中,越具体的异常越要靠前,越普遍的异常越靠后。 如果抛出的异常不在catch字句中,该异常则无法处理,会被升级到调用者处。
捕捉异常的catch子句,语法与其他语言中不太一样。在Scala里,借用了模式匹配的思想来做异常的匹配,因此,在catch的代码里,是一系列case字句,如下例所示。
import java.io._ import java.lang.SecurityException try { val file = new File("myfile") val stream = new FileInputStream(file) // use the stream } catch { case ex: FileNotFoundException => println("File not found") case ex: SecurityException => println("You don‘t have read access to the file") case _: Throwable => println("Other exception happened") }
catch字句里的内容跟match里的case是完全一样的。由于异常捕捉是按次序,如果最普遍的异常,Throwable,写在最前面,则在它后面的case都捕捉不到,因此需要将它写在最后面。
5.3.finally字句
finally字句用于执行不管是正常处理还是有异常发生时都需要执行的步骤,一般用于对象的清理工作。如下面伪代码所示,Scala的finally跟其他语言是一样的。
val file = getFile try { //use the file } catch { //handle exception } finally { //close the file no matter how above code goes }
中断循环
Scala中没有break或continue关键字,这代表Scala不鼓励使用break或continue跳出循环。不过,如果实在想用的话,Scala提供了相应的类和方法。
使用break
如果确有需要,可以使用Breaks类的break方法,来实现退出循环的功能。如下例所示。
import scala.util.control.Breaks._ val list = List("functions.md", "menu.json", "index.html", "data.xml") var found = false breakable { for(item <- list) { if(item.endsWith("html")) { found = true break } } } if(found) println("There is at least one html file in the list") else println("There is no html file in the list")
类似这样的代码,我们在指令式编程中,比如Java,C语言等,经常会看到。但是实际实现上还是有差别的。Scala的break是Breaks类的一个方法,所以,上述代码首先引入了Breaks类。
import scala.util.control.Breaks._
另外,break实际上是靠的抛出一个异常来实现的,源代码是
def break(): Nothing = { throw breakException }
这里,breakException是一个ControlThrowable类型的异常。因此,你的代码还需要用breakable包围起来。breakable的作用是捕获break抛出的异常,以免影响你真正的产品代码。其源代码类似于
def breakable(op: => Unit) { try { op } catch { case ex: BreakControl => if (ex ne breakException) throw ex } }
在Scala里使用break真的不太方便,continue呢,则压根儿就没有,所以,咱最好还是别用了。这正是Scala的设计者所期待的,避免使用break和continue。
避免使用break和continue
避免使用break和continue完全是可以做到的。比较简单和符合我们编程固有习惯的方式,就是用while循环,将终止条件放到while字句里,比如上面的例子,要修改的话,可以将found放进while字句。
var found = false while(!found) { for(item <- list) { if(item.endsWith("html")) { found = true } } }
以上是一种可行的办法,但是,还不是Scala设计者希望看到的。他们希望看到的是程序员们能具有函数式编程的思想。还是拿上面的例子来说,从函数的角度考虑, 以上所需要完成的功能,用一个函数来表达,就是在一个字符串列表中寻找以html结尾的字符串,这个函数可以定义如下面的hasHtml。
val list = List("functions.md", "menu.json", "index.html", "data.xml") def hasHtml(input: List[String]): Boolean = { input match { case Nil => false case x :: sub => { if(x.endsWith("html")) return true else hasHtml(sub) } } } if(hasHtml(list)) println("There is at least one html file in the list") else println("There is no html file in the list")
hasHtml是比较容易理解的,如果list里面没有元素,当然是false,然后将list看成两部分,第一个元素,和剩余的子列表。 如果第一个元素是html的,那么就已经找到了,如果不是,则继续在子列表里面查找。这样的思考问题方式,比较容易理解和交流。 在子列表查找,和原始问题是一样的,只是输入集在缩小,这是一个典型的递归问题。事实上,函数式编程几乎离不开递归。 更特别的,以上hasHtml是一个尾递归,大部分函数式语言编译器都对尾递归有优化,Scala也不例外。对程序员来说,不用担心使用尾递归会带来性能下降。 关于尾递归,后续文章会进行讨论。
从指令式的思维转变到函数式,需要时间,不要强求瞬间完成。不过从现在开始,我们可以慢慢体会函数式编程的优雅。
参考文献:
http://meetfp.com/zh/scala-basic/break