Control
使用 for 与 foreach 循环
如果需要迭代集合中的元素,或者操作集合中的每个元素,或者是通过已有的集合创建新集合,都可以使用 for 和 foreach 来处理。
for (elem <- collection) operationOn(elem)
或者从循环中生成值:
val newArray = for (e <- a) yield a.toUpperCase
生成的集合类型与输入的集合类型一致,比如一个 Array 会生成一个 Array。
或者通过一个计数器访问集合中的元素;
for (i <- 0 until a.length) println(s"$i is ${a(i)}")
或者使用集合提供的zipWithIndex方法,然后访问索引与元素:
for ((e, count) <- a.zipWithIndex) (s"$count is $e")
或者使用守卫了限制处理条件:
for (i <- 1 to 10 if i < 4) println(i)
或者处理一个 Map:
val names = Map("fname" -> "Robert", "lname" -> "Goren")
for ((k,v) <- names) println(s"key: $k, value: $v")
在使用 for/yield 组合时,实际上是在创建一个新的集合,如果只是使用 for 循环则不会创建新的集合。for/yield 的处理过程类似于 map 操作。还有一些别的方法,如:foreach、map、flatMap、collect、reduce等都能完成类似的工作,可以根据需求选择合适的方法。
另外,还有一些特殊的使用方式;
// 简单的处理
a.foreach(println)
// 使用匿名函数
a.foreach(e => println(e.toUpperCase))
// 使用多行的代码块
a.foreach{ e =>
  val s = e.toUpperCase
  println(s)
}
工作原理
for 循环的工作原理:
- 一个简单的 for 循环对整个集合的迭代转换为在该集合上 foreach 方法的调用
 - 一个带有守卫的 for 循环对整个集合的迭代转换为集合上 withFilter 方法的调用后跟一个 foreach 方法调用
 - 一个 for/yield 组合表达式被转换为该集合上的 map 方法调用
 - 一个带有守卫的 for/yield 组合表达式被转换为该集合上的 withFilter 方法调用后跟 map 方法调用
 
通过命令scalac -Xprint:parse Main.scala可以查看 Scala 对 for 循环的具体转换过程。
在多个 for 循环中使用多个计数器
可以在 for 循环中同时使用多个计数器:
for {
  i <- 1 to 2
  j <- 1 to 2
} println(s"i = $i, j = $j")
for 循环中的<-标示符被引用为一个生成器(generator)。
在 for 循环中使用守卫
有多种风格可以选择:
for (i <- 1 to 10 if i % 2 == 0) println(i)
for { 
  i <- 1 to 10
  if i % 2 == 0 
} println(i)
或者使用传统的方式:
for (file <- files){
  if (hasSoundFileExtension(file) && !soundFileIsLong(file))
  soundFiles += file
}
再或者可读性更强的方式:
for {
  file <- files
  if passFilter1(file)
  if passFilter2(file)
} doSomething(file)
因为 for 循环会被转换为一个 foreach 的方法调用,可以直接使用 withFilter 然后调用 foreach 方法,也能达到同样的效果。
for/yield 组合
for/yield 组合可以通过在已有的集合的没有元素上应用一个新的算法(或转换等)来生成一个新的集合,并且,新的集合类型与输入集合保持一致。
val names = Array("chris", "ed", "maurice")
val capNames = for (e <- names) yield e.capitalize
// Array(Chris, Ed, Maurice)
如果对每个元素的应用部分需要多行,可以使用{}来组织代码块:
val capNames = for (e <- names) yield {
  // multi lines
  e.capitalize
}
实现 break 和 continue
在 Scala 中并没有提供 break 和 continue 关键字,但是通过scala.util.control.Breaks提供了类似的功能:
import util.control.Breaks._
object BreakAndContinueDemo extends App {
  breakable{
    for (i <- 1 to 10){
      println(i)
      if (i > 4) break	// 跳出循环
	}
  }
  
  val searchMe = "peter piper picked a peck of pickled peppers"
  var numps = 0
  for (i <- 0 until seachMe.length){
    breakable{
      if (searchMe.charAt(i) != 'p'){
  		break 		// 跳出 breakable, 而外层的循环 continue
	  } else {
   		numps += 1
	  }
	}
  }
  println("Found " + numPs + " p's in the string.")
}
在 Breaks 源码的定义当中:
def break(): Nothing = { throw breakException }
def breakable(op: => Unit) {
  try {
	op
  } catch {
    case ex: BreakControl =>
      if (ex ne breakException) throw ex
  }
}
break 的调用实际是抛出一个异常,breakable 部分会对捕捉这个异常,因此也就达到了“跳出循环”的效果。
而在上面例子的第二部分,breakable 实际上是控制的 if/else 部分,当满足了 if 的条件,执行 break,否则执行 numps += 1,即在不能满足使numps +=1的条件时跳过了当前元素,从而达到 continue 效果。
通用语法
break:
breakable { for (x <- xs) { if (cond) break } }
continue:
for (x <- xs) { breakable { if (cond) break } }
有些场景需要处理嵌套的 break:
object LabledBreakDemo extends App {
  import scala.util.control._
  
  val Inner = new Breaks
  val Outer = new Breaks
  
  Outer.breakable{
    for (i <- 1 to 5){
      Inner.breakable{
  		for (j <- 'a' to 'e') {
  		  if (i == 1 && j == 'c') Inner.break else println(s"i: $i, j: $j")
  		  if (i == 2 && j == 'b') Outer.break
		}
	  }
	}
  }
}
其他方式
如果不想使用 break 这样的语法,还有其他的方式可是实现。
通过在外部设置一个标记,满足条件是设定该标记,而执行时检查该标记:
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))
使用 if 实现三目运算符
val absValue = if (a < 0) -a else a
println(if (i == 0) "a" else "b")
hash = hash * prime + (if (name == null) 0 else name.hashCode)
if 表达式会返回一个值。
使用 match 语句
val month = i match { 
  case 1 => "January" 
  case 2 => "February" 
  case 3 => "March" 
  ...
  case _ => "Invalid month"
}
当把 match 作为一个 switch 功能使用时,推荐的做法是使用@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" 
  }
}
在一个 case 语句中匹配多个条件
如果有些场景中,多个不同条件都属于同一个业务逻辑,这时可以在一个 case 语句中添加多个条件,使用符号|分割,各种条件的关系为或:
val i = 5 
i match { 
  case 1 | 3 | 5 | 7 | 9 => println("odd") 
  case 2 | 4 | 6 | 8 | 10 => println("even") 
}
将匹配表达式的结果赋给你个变量
匹配语句的结果可以作为一个值赋值给一个变量:
val evenOrOdd = someNumber match { 
  case 1 | 3 | 5 | 7 | 9 => println("odd") 
  case 2 | 4 | 6 | 8 | 10 => println("even") 
}
访问匹配语句中默认 case 的值
如果想要访问默认 case 的值,需要使用一个变量名将其绑定,而不能使用通配符_:
i match { 
  case 0 => println("1") 
  case 1 => println("2") 
  case default => println("You gave me: " + default) 
}
在匹配语句中使用模式匹配
匹配语句中可以使用多种模式,比如:常量模式、变量模式、构造器模式、序列模式、元组模式或类型模式。
def echoWhatYouGaveMe(x:Any):String = x match{
  // 常量模式
  case 0 => "zero"
  case true => "true"
  case "hello" => "hello"
  case Nil => "an empty list"
  
  // 序列模式
  case List(0,_,_) => "一个长度为3的列表,且第一个元素为0"
  case List(1,_*) => "含有多个元素的列表,且第一个元素为1"
  case Vector(1, _*) => "含有多个元素的 Vector,且第一个元素为1"
  
  // 元组模式
  case (a, b) => "匹配 2 元组模式"
  case (a,b,c) => "匹配 3 元组模式"
  
  // 构造器模式
  case Person(first, "Alexander") => "匹配一个 Person,第二个字段为 Alexander,并绑定第一个字段到变量 first 上"
  case Dog("Suka") => "匹配一个 Dog,却字段值为 Suka"
  case obj @ Some(value) => "匹配一个 Some 并取出构造器中的值,同时将整个对象绑定到变量 obj"
  
  // 类型模式
  case s:String => "String"
  case i: Int => "Int"
  ...
  case d: Dog => "匹配任何 Dog 类型,并将该对象绑定到变量 d"
  
  // 通配模式
  case _ => "匹配所有上面没有匹配到的值"
}
在匹配表达式中使用 case 类
匹配 case 类或 case 对象的多种方式,用法的选择取决于你要在 case 语句右边使用哪部分值:
trait Animal 
case class Dog(name: String) extends Animal 
case class Cat(name: String) extends Animal 
case object Woodpecker extends Animal
object CaseClassTest extends App {
  def determiType(x:Animal):String = x match {
    case Dog(moniker) => "将 name 字段的值绑定到变量 moniker"
    case _:Cat => "仅匹配所有的 Cat 类"
    case Woodpecker => "匹配 Woodpecker 对象"
    case _ => "通配"
  }
}
匹配语句中使用守卫
可以给每个单独的匹配语句添加额外的一个或多个守卫:
i match { 
  case a if 0 to 9 contains a => println("0-9 range: " + a) 
  case b if 10 to 19 contains b => println("10-19 range: " + b) 
  case c if 20 to 29 contains c => println("20-29 range: " + c) 
  case _ => println("Hmmm...") 
}
或者是将一个对象的不同条件分拆到多个 case 语句:
num match { 
  case x if x == 1 => println("one, a lonely number") 
  case x if (x == 2 || x == 3) => println(x) 
  case _ => println("some other value") 
}
使用匹配语句代替 isInstanceOf
如果需要匹配一个类型或多种不同的类型,虽然可以使用isInstanceOf来进行类型的判断,但这样并不遍历同时也不提倡这种用法:
if (x.isInstanceOf[Foo]) { do something ...
更好的方式是使用匹配语句:
trait SentientBeing 
trait Animal extends SentientBeing 
case class Dog(name: String) extends Animal 
case class Person(name: String, age: Int) extends SentientBeing
// later in the code ... 
def printInfo(x: SentientBeing) = x match { 
  case Person(name, age) => // handle the Person 
  case Dog(name) => // handle the Dog 
}
使用匹配语句处理 List
List 结构与其他的集合结构有点不同,它以常量单元开始,以 Nil 元素结束。
val x = List(1,2,3)
val y = 1 :: 2 :: 3 :: Nil
在编写递归算法时,可以利用最后一个元素为 Nil 对象的便利。比如下面的 listToString 方法,如果当前的元素不是 Nil,则继续递归调用列表剩余的部分。一旦当前元素为 Nil,停止递归调用并返回一个空字符串:
def listToString(list: List[String]): String = list match { 
  case s :: rest => s + " " + listToString(rest) 
  case Nil => "" 
}
可以用同样的方式来递归求所有元素之和:
def sum(list: List[Int]): Int = list match { 
  case Nil => 1 
  case n :: rest => n + sum(rest) 
}
或者元素之积:
def multiply(list: List[Int]): Int = list match { 
  case Nil => 1 
  case n :: rest => n * multiply(rest) 
}
注意,这些用法必须记得要处理 Nil 元素。
在 try/catch 中处理多种异常
try {
  openAndReadAFile(filename) 
} catch { 
  case e: FileNotFoundException => println("Couldn't find that file.") 
  case e: IOException => println("Had an IOException trying to read that file") 
}
或者并不关心异常的种类,可以使用一个高阶异常类型来捕获可能的异常:
try {
  openAndReadAFile("foo") 
} catch {
  case t: Throwable => t.printStackTrace() 
}
或者:
try {
  val i = s.toInt 
} catch {
  case _: Throwable => println("exception ignored") 
}
Java 中可以在 catch 部分抛出异常,但是 Scala 中没有受检异常,不必指定一个方法会抛出的异常:
def toInt(s: String): Option[Int] = try {
    Some(s.toInt) 
  } catch {
	case e: Exception => throw e 
  }
如果想要声明抛出的异常类型,或者要与 Java 集成,可以使用@throws标注方法的异常类型:
@throws(class[NumberFormatException])
def toInt(s: String): Option[Int] = try {
    Some(s.toInt) 
  } catch {
	case e: Exception => throw e 
  }
在 try/catch/finally 语句块之外声明一个变量
如果需要在 Try 语句块内使用一个变量,并且需要在最后的 finally 块中访问,比如一个资源对象需要在 finally 中关闭:
object CopyBytes extends App {
  var in = None: Option[FileInputStream] 
  var out = None: Option[FileOutputStream]
  try { 
    in = Some(new FileInputStream("/tmp/Test.class")) 
    out = Some(new FileOutputStream("/tmp/Test.class.copy")) 
    var c = 0 
    while ({c = in.get.read; c != −1}) {
      out.get.write(c) 
    }
  } catch {
	case e: IOException => e.printStackTrace 
  } finally { 
    println("entered finally ...") 
    if (in.isDefined) in.get.close 
    if (out.isDefined) out.get.close 
  }
}
或者使用更简洁的方式:
try { 
  in = Some(new FileInputStream("/tmp/Test.class")) 
  out = Some(new FileOutputStream("/tmp/Test.class.copy")) 
  in.foreach { inputStream => 
    out.foreach { outputStream => 
      var c = 0 
      while ({c = inputStream.read; c != −1}) { 
        outputStream.write(c)
      }
    }
  }
}
自定义控制结构
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.