8.5 for
表达式
foreach()
方法为容器提供了内部迭代器——你不必控制循环,只要提供在每个迭代的上下文里执行的代码即可。不过,如果要控制循环,或是同时处理多个容器,就要使用外部迭代器了,也就是for()
表达式。看个简单的循环:
ScalaIdioms/PowerOfFor.scala
for (i <- 1 to 3) { print("ho ")}
上面的代码会打印出“ho ho ho
”。它是下面这个表达式通用语法的简写形式:
for([pattern <- generator; definition*]+; filter*)
[yield] expression
for
表达式接收的参数包括一个或多个生成器(generator),0
或多个定义(definition),还有0
或多个过滤器(filter)。这些东西彼此以分号分隔。yield
关键字是可选的,如果它存在的话,就表示让表达式返回一组值而不是一个Unit
。这里面有一大堆细节,不过不必担心,因为我们会用例子来讲述。这样用不了多长时间,你就可以很适应了。
先从yield
开始。假定要取出一个范围内的值,每个值乘以2
。代码如下:
ScalaIdioms/PowerOfFor.scala
val result = for (i <- 1 to 10)
yield i * 2
上面代码返回一个包含很多值的容器,其中每个值都是给定范围(1
到10
)的值的2倍。
上面的逻辑也可以用map()
方法执行,像这样:
ScalaIdioms/PowerOfFor.scala
val result2 = (1 to 10).map(_ * 2)
幕后,Scala会把for
表达式翻译成另外一个表达式,根据表达式复杂度组合使用map()
和filter()
。
现在,假定只对范围内的偶数翻倍,可以用过滤器:
ScalaIdioms/PowerOfFor.scala
val doubleEven = for (i <- 1 to 10; if i % 2 == 0)
yield i * 2
上面的for
表达式可以读作“返回i * 2
的容器,其中i
是给定范围内的成员且i
是偶数”。这样,前面的表达式更像对值容器的SQL查询——在函数式编程里,称为list comprehension。
如果觉得上面代码里分号太麻烦,影响代码的简洁,可以用大括号替换括号,这样就可以丢掉分号了,像这样:
for {
i <-1 to 10
if i %2 == 0
}
yield i * 2
生成器里还可以放置变量定义。在每个迭代里,Scala都会以这个名字定义一个新的val。
下面例子里,对Person
的容器进行迭代,打印他们的姓:
ScalaIdioms/Friends.scala
class Person(val firstName: String, val lastName: String)
object Person {
def apply(firstName: String, lastName: String) : Person =
new Person(firstName, lastName)
}
val friends = List(Person("Brian", "Sletten"), Person("Neal", "Ford"),
Person("Scott", "Davis"), Person("Stuart", "Halloway"))
val lastNames = for (friend <- friends; lastName = friend.lastName)
yield lastName
println(lastNames.mkString(", "))
上面代码的输出如下:
Sletten, Ford, Davis, Halloway
上面代码也是一个Scala语法糖的例子,暗地里用到了apply()
方法——代码简洁而可读,这段代码会创建出一个新的Person
的list
。
如果在for
表达式里提供了多个生成器,则每个生成器都会形成一个内部循环,最右的生成器控制着最内的循环。下面的例子里,用到两个生成器:
ScalaIdioms/MultipleLoop.scala
for (i <-1 to 3;j <-4 to 6) {
print("[" + i + "," +j+ "] ")
}
上面代码的输出如下:
[1,4] [1,5] [1,6] [2,4] [2,5] [2,6] [3,4] [3,5] [3,6]
在本章里,我们学会了使用Scala提供的3种主要的容器,也见识了for()
表达式和list comprehension的威力。接下来,我们会学到模式匹配——Scala最为强大的特性之一。