Scala开发教程(13): for 表达式

jerry Scala 2015年11月25日 收藏

Scala中的for表达式有如一把完成迭代任务的瑞士军刀,它允许你使用一些简单的部件以不同的方法组合可以完成许多复杂的迭代任务。简单的应用比如枚举一个整数列表,较复杂的应用可以同时枚举多个不同类型的列表,根据条件过滤元素,并可以生成新的集合。

枚举集合元素
这是使用for表示式的一个基本用法,和Java的for非常类型,比如下面的代码可以枚举当前目录下所有文件:

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

其中如 file < ? filesHere 的语法结构,在Scala中称为“生成器 (generator)”。 中filesHere 的类型为 Array[File], 每次迭代 变量file 会初始化为该数组中一个元素, File的toString()为文件的文件名,因此println(file)打印出文件名。 Scala的for 表达式支持所有类型的集合类型,而不仅仅是数组,比如下面使用for 表达式来枚举一个Range类型。

  1. scala> for ( i | println ("Interation " + i)
  2. Interation 1
  3. Interation 2
  4. Interation 3
  5. Interation 4
  6.  

过滤
某些时候,你不想枚举集合中的每个元素,而是只迭代某些符合条件的元素,在Scala中,你可以为for表达式添加一个过滤器?在for的括号内添加一个if语句,例如:
修改前面枚举文件的例子,改成只列出.scala文件如下:

  1. val filesHere = (new java.io.File(".")).listFiles
  2.  
  3. for( file println(file)

如果有必要的话,你可以使用多个过滤器,只要添加多个if语句即可,比如,为保证前面列出的文件不是目录,可以添加一个if,如下面代码:

  1. val filesHere = (new java.io.File(".")).listFiles
  2.  
  3. for( file <-filesHere
  4. if file.isFile
  5. if file.getName.endsWith(".scala")
  6. ) println(file)

嵌套迭代
for表达式支持多重迭代,下面的例子使用两重迭代,外面的循环枚举filesHere,而内部循环枚举该文件的每一行文字。实现了类似Unix grep命令:

  1. val filesHere = (new java.io.File(".")).listFiles
  2.  
  3. def fileLines (file : java.io.File) =
  4. scala.io.Source.fromFile(file).getLines().toList
  5.  
  6. def grep (pattern: String) =
  7. for (
  8. file if file.getName.endsWith(".scala");
  9. line <-fileLines(file)
  10. if line.trim.matches(pattern)
  11. ) println(file + ":" + line.trim)
  12.  
  13. grep (".*gcd.*")
  14.  

注意上面代码中两个迭代之间使用了?;?,如果你使用{} 替代for的()的括号,你可以不使用“;”分隔这两个“生成器”,这是因为Scala编译器不推算包含在括号内的省掉的“;?。使用{}改写的代码如下:

  1. val filesHere = (new java.io.File(".")).listFiles
  2.  
  3. def fileLines (file : java.io.File) =
  4. scala.io.Source.fromFile(file).getLines().toList
  5.  
  6. def grep (pattern: String) =
  7. for {
  8. file if file.getName.endsWith(".scala")
  9. line <-fileLines(file)
  10. if line.trim.matches(pattern)
  11. } println(file + ":" + line.trim)
  12.  
  13. grep (".*gcd.*")

这两段代码是等效的。

绑定中间变量
你可以注意到前面代码使用了多次line.trim,如果trim是个耗时的操作,你可以希望trim只计算一次,Scala允许你使用=号来绑定计算结果到一个新变量。绑定的作用和val类似,只是不需要使用val关键字,例如,修改前面的例子,只计算一次trim,把结果保存在trimmed变量中。

  1. val filesHere = (new java.io.File(".")).listFiles
  2.  
  3. def fileLines (file : java.io.File) =
  4. scala.io.Source.fromFile(file).getLines().toList
  5.  
  6. def grep (pattern: String) =
  7. for {
  8. file if file.getName.endsWith(".scala")
  9. line <-fileLines(file)
  10. trimmed=line.trim
  11. if trimmed.matches(pattern)
  12. } println(file + ":" + trimmed)
  13.  
  14. grep (".*gcd.*")
  15.  

生成新集合
for 表达式也可以用来生产新的集合,这是Scala的for表达式比Java的for语句功能强大的地方。它的基本语法如下:
for clauses yield body
关键字yield放在body的前面,for 没迭代一次,产生一个body,yield收集所有的body结果,返回一个body类型的集合。比如,前面列出所有.scala文件,返回这些文件的集合:

  1. def scalaFiles =
  2. for {
  3. file if file.getName.endsWith(".scala")
  4. } yield file
  5.  

scalaFiles的类型为Array[File].