8.5 for表达式

foreach()方法为容器提供了内部迭代器——你不必控制循环,只要提供在每个迭代的上下文里执行的代码即可。不过,如果要控制循环,或是同时处理多个容器,就要使用外部迭代器了,也就是for()表达式。看个简单的循环:

ScalaIdioms/PowerOfFor.scala

  1. for (i <- 1 to 3) { print("ho ")}

上面的代码会打印出“ho ho ho”。它是下面这个表达式通用语法的简写形式:

  1. for([pattern <- generator; definition*]+; filter*)
  2. [yield] expression

for表达式接收的参数包括一个或多个生成器(generator),0或多个定义(definition),还有0或多个过滤器(filter)。这些东西彼此以分号分隔。yield关键字是可选的,如果它存在的话,就表示让表达式返回一组值而不是一个Unit。这里面有一大堆细节,不过不必担心,因为我们会用例子来讲述。这样用不了多长时间,你就可以很适应了。

先从yield开始。假定要取出一个范围内的值,每个值乘以2。代码如下:

ScalaIdioms/PowerOfFor.scala

  1. val result = for (i <- 1 to 10)
  2. yield i * 2

上面代码返回一个包含很多值的容器,其中每个值都是给定范围(110)的值的2倍。

上面的逻辑也可以用map()方法执行,像这样:

ScalaIdioms/PowerOfFor.scala

  1. val result2 = (1 to 10).map(_ * 2)

幕后,Scala会把for表达式翻译成另外一个表达式,根据表达式复杂度组合使用map()filter()

现在,假定只对范围内的偶数翻倍,可以用过滤器:

ScalaIdioms/PowerOfFor.scala

  1. val doubleEven = for (i <- 1 to 10; if i % 2 == 0)
  2. yield i * 2

上面的for表达式可以读作“返回i * 2的容器,其中i是给定范围内的成员且i是偶数”。这样,前面的表达式更像对值容器的SQL查询——在函数式编程里,称为list comprehension。

如果觉得上面代码里分号太麻烦,影响代码的简洁,可以用大括号替换括号,这样就可以丢掉分号了,像这样:

  1. for {
  2. i <-1 to 10
  3. if i %2 == 0
  4. }
  5. yield i * 2

生成器里还可以放置变量定义。在每个迭代里,Scala都会以这个名字定义一个新的val。

下面例子里,对Person的容器进行迭代,打印他们的姓:

ScalaIdioms/Friends.scala

  1. class Person(val firstName: String, val lastName: String)
  2. object Person {
  3. def apply(firstName: String, lastName: String) : Person =
  4. new Person(firstName, lastName)
  5. }
  6. val friends = List(Person("Brian", "Sletten"), Person("Neal", "Ford"),
  7. Person("Scott", "Davis"), Person("Stuart", "Halloway"))
  8. val lastNames = for (friend <- friends; lastName = friend.lastName)
  9. yield lastName
  10. println(lastNames.mkString(", "))

上面代码的输出如下:

  1. Sletten, Ford, Davis, Halloway

上面代码也是一个Scala语法糖的例子,暗地里用到了apply()方法——代码简洁而可读,这段代码会创建出一个新的Personlist

如果在for表达式里提供了多个生成器,则每个生成器都会形成一个内部循环,最右的生成器控制着最内的循环。下面的例子里,用到两个生成器:

ScalaIdioms/MultipleLoop.scala

  1. for (i <-1 to 3;j <-4 to 6) {
  2. print("[" + i + "," +j+ "] ")
  3. }

上面代码的输出如下:

  1. [1,4] [1,5] [1,6] [2,4] [2,5] [2,6] [3,4] [3,5] [3,6]

在本章里,我们学会了使用Scala提供的3种主要的容器,也见识了for()表达式和list comprehension的威力。接下来,我们会学到模式匹配——Scala最为强大的特性之一。