Scala的内建控制结构

时间:2022-09-08 17:56:29

Scala中的内建控制机构仅有if、while、for、try、match和函数调用。虽然Scala的基础控制结构少,但也足以支持指令式语言里所有的实质内容。因为它们都能产生值,有助于缩短代码。
程序员可以通过使用返回值的控制结构简化代码,避免创建临时变量来保存控制结构中的计算结果。

1 If表达式

1.1.常规式

var filename="default"
if(!args.isEmpty)
  filename=args(0)

1.2.scala里根据条件做初始化的例子

val filename=
  if(!args.isEmpty)  args(0)
  else  "default.txt"

这样写有两个好处:
一是使用val体现了函数式风格,并具有java的final变量类似的效果。它告诉读者,代码变量将不再改变,从而节省了他们审查变量作用域的所有代码和检查它是否改变的工作。
二是更好地支持等效推论。在表达式没有副作用的前提下,引入的变量等效于计算它的表达式。无论何时都可以用表达式替换变量名。

尽可能使用val,它能让你的代码既容易阅读又容易重构。

2.While循环

2.1常规式

while循环包括状态判断和循环体,只要状态保持为真,循环体一遍遍被执行。

def gcdLoop(x:Long, y:Long):Long={
  var a=x
  var b=y
  while(a!=0) {
    val temp = a
    a = b%a
    b=temp
  }
  b
}

例如Do-While循环

var line =""
do {
  line=readLine()
  println("Read: "+line)
} while(line!="")

while和do-while结构之所以称之为“循环”,而不是表达式,因为他们不能产生有意义的结果,结果类型只能是Unit类型,是表明存在且唯一存在类型为Unit的值,写成()。()的存在是scala的Unit与Java的void不同的地方,例如

def greet() {println("hi")}
greet()==()

以上代码将返回true。因为greet返回unit值()。所有greet的结果和unit值()相等,返回true。

另外需要注意的是,赋值等式本身也是unit值
例如

var line=""
while((line=readLine())!="")  //不起作用
  println("Read: "+line)

因为赋值语句line=readLIne()的值将永远返回()而不是”“

建议你在代码中更为审慎地使用while循环。如果对while或do循环没有非用不可的理由,请尝试别的方式实现同样的功能。

3.For表达式

scala的For表达式是枚举操作的瑞士军刀。

3.1枚举集合类

val filesHere=(new java.io.File(".")).listFiles
for(file<- filesHere)
  println(file)

通过被称为发生器语法“file<-filesHere”,遍历了filesHere数组的元素。

for表达式语法对任何种类的集合类都有效,而不只是数组。例如Range

for(i<-1 to 4)
  println("Iteration "+i)

以下这种方式遍历数组不常用:

for(i<-0 to filesHere.length-1)
  println(filesHere(i))

不常用的原因是由于集合本身可以直接被枚举,并且不会出现溢出。

3.2 过滤

又是不想枚举处全部元素,只想过滤出某个子集。这可以通过for表达式的括号中添加过滤器(filter),即if子句。例如过滤以.scala为结尾的文件名:

val filesHere = (new java.io.File(".")).listFiles
for(file<-filesHere if file.getName.endsWith(".scala"))
  println(file)

也可以加入更多的过滤器,只要不断添加if子句即可。如果发生器中超过一个过滤器,if子句必须用分号分隔。

for(
  file<-fileHere
  if file.isFile;
  if file.getName.endsWith(".scala")
) println(file)

3.3嵌套枚举

如果加入多个<-子句,就可以嵌套循环。例如:

def fileLines(file:java.io.File)=
  scala.io.Source.fromFile(file).getLines.toList

def grep(pattern:String)=
  for(
    file<-fileHere
    if file.getName.endsWith(".scala")
    line<-fileLines(file)
    if line.trim.mathches(pattern)
  ) println(file+": "+line.trim)

grep(".*gcd.*")

3.4 流间变量绑定

在前面的代码中重复出现了表达式line.trim。如果想要只计算一遍,可以通过等号=把结果绑定到新的变量。绑定变量被当做val引入和使用,但是不带关键字val。

def grep(pattern:String)=
  for(
    file<-fileHere
    if file.getName.endsWith(".scala")
    line<-fileLines(file)
    trimmed=line.trim
    if trimmed.mathches(pattern)
  ) println(file+": "+trimmed)

grep(".*gcd.*")

3.5 制造新的集合

到现在为止所有例子只是对枚举值进行操作然后释放,除此以为,还可以创建一个值去记住每一次的迭代,只要在for表达式之前加上关键字yield。比如:

def scalaFiels=
  for{
    file<-filesHere
    if file.getName.endsWith(".scala")
  } yield file

for表达式在每次执行的时候都会产生一个新值,本例中是file。当for表达式完成的时候,结果将是包含了产生值的集合对象。对象的类型基于枚举子句处理的集合类型。本例中的结果为Array[File],因为filesHere是数组并且产生的表达式类型是File。
另外注意yield一定放在左括号之前,而不是代码块的最后一个表达式之前。

for {子句} yield {循环体}

val forLineLengths=
  for {
    file<- filesHere
    if file.getName.endsWith(".scala")
    line<-fileLines(file)
    trimmed = line.trim
    if trimmed.matches(".*for.*")
  } yield trimmed.length

4使用try表达式处理异常

Scala中方法处理能返回值以外,还可以通过抛出异常中止执行。方法的调用者要么可以捕获并处理这个异常,或者也可以简单的终止掉,并把异常上升到调用者的调用者处。

4.1抛出异常

throw也是有结果类型的表达式,例如:

val half=
 if (n%2==0)
   n/2
 else 
   throw new RuntimeException("n must be even")

异常抛出的类型是Nothing。
像这样的例子经常使用。if的一个分支计算值,另一个爆出异常并得出Nothing。

4.2捕获异常

在scala中捕获异常时,在catch子句中经常使用case模式匹配,这是scala的特色。模式匹配是一种很强大的特征。

import java.io.FileReader
import java.io.FileNotFoundException
import java.io.IOException
try{
  val f= new FileReader("inpit.txt")
}  catch{
  case ex:FileNotFoundException=>//处理丢失的文件
  case ex:IOException=> //处理其他的IO错误

Scala里不需要捕获检查异常,或者把他们生命在throws子句中。

4.3 finally子句

如果要让某些代码无论如何都要执行的话,可以放在finall子句中。例如:

import java.io.FileReader

val file=new FileReader("input.txt")
try{
  //使用文件
}finally {
  file.close()  //确保关闭文件
}

在Scala中还可以使用另一种被称为出借模式(loan pattern)的技巧更简洁地达到同样的目的。

def withPrintWriter(file:File,op:PrintWriter=>Unit) {
  val writer= new PrintWriter(file)
  try {
    op(writer)
  } finally {
    writer.close()
  }
}

上面的代码中,由withPrintWriter而并非用户代码,确认文件在结尾被关闭。因为不可能忘记关闭文件。因为控制抽象函数,打开了资源并借贷给函数。当用户函数完成时,它发出信号说明它不再需要借的资源,于是资源被关闭在finally中,以确信其确实被关闭。

4.4生成值

try-catch-finally也产生值,但是finally子句应当做关闭文件之类的清理工作,他们不应该修改主函数体或catch子句中计算的值。

scala与java的try-finally最大的不同是,在java中finally子句不产生值。

def f():Int=try{return 1} finally {return 2}
def g():Int=try{1} finally{2}

调用f()返回2,调用g()返回1。

5.匹配表达式

scala中的match类似于java中的swith,但是他可以在提供多个备选项中做出选择。默认情况下用_说明。例如:

val firstArg = if(args.length<0) args(0) else ""

firstArg match{
  case "salt"=> println("pepper")
  case "chips"=> println("salsa")
  case "eggs"=> println("bacon")
  case _ =>println("huh?")

与Java相比,Scala的匹配表达式还有一些重要差别。
(1)任何类型的常量,或者其他东西都可以成为case,而不只是java中的整数类型和枚举常量。
(2)每个备选项的最后没有break,因为break是隐含的。
(3)match也能产生值。例如:

val firstArg = if(!args.isEmpty) args(0) else ""

val firend= firstArg match{
  case "salt"=> println("pepper")
  case "chips"=> println("salsa")
  case "eggs"=> println("bacon")
  case _ =>println("huh?")
}
println (frined)