元组和集合
在 Scala 创建一个元组类型非常的简单,这只是主体的一部分:如果首先将元素公开给外部,那么在类型内部创建描述这些元素的名称就毫无价值。考虑清单 3:
清单 3. tuples.scala
// JUnit test suite // class TupleTest { import org.junit._, Assert._ import java.util.Date @Test def simpleTuples() = { val tedsStartingDateWithScala = Date.parse("3/7/2006") val tuple = ("Ted", "Scala", tedsStartingDateWithScala) assertEquals(tuple._1, "Ted") assertEquals(tuple._2, "Scala") assertEquals(tuple._3, tedsStartingDateWithScala) } }
创建元组非常简单,将值放入一组圆括号内,就好象调用一个方法调用一样。提取这些值只需要调用 “_n” 方法,其中 n 表示相关的元组元素的位置参数:_1 表示第一位,_2 表示第二位,依此类推。传统的 Java java.util.Map 实质上是一个分两部分的元组集合。
元组可以轻松地实现使用单个实体移动多个值,这意味着元组可以提供在 Java 编程中非常重量级的操作:多个返回值。例如,某个方法可以计算 String 中字符的数量,并返回该 String 中出现次数最多的字符,但是如果程序员希望同时 返回最常出现的字符和 它出现的次数,那么程序设计就有点复杂了:或是创建一个包含字符及其出现次数的显式类,或将值作为字段保存到对象中并在需要时返回字段值。无论使用哪种方 法,与使用 Scala 相比,都需要编写大量代码;通过简单地返回包含字符及其出现次数的元组,Scala 不仅可以轻松地使用 “_1”、“_2” 等访问元组的各个值,还可以轻松地返回多个返回值。
如下节所示,Scala 频繁地将 Option 和元组保存到集合(例如 Array[T] 或列表)中,从而通过一个比较简单的结构提供了极大的灵活性和威力。
创建元组非常简单,将值放入一组圆括号内,就好象调用一个方法调用一样。提取这些值只需要调用 “_n” 方法,其中 n 表示相关的元组元素的位置参数:_1 表示第一位,_2 表示第二位,依此类推。传统的 Java java.util.Map 实质上是一个分两部分的元组集合。
元组可以轻松地实现使用单个实体移动多个值,这意味着元组可以提供在 Java 编程中非常重量级的操作:多个返回值。例如,某个方法可以计算 String 中字符的数量,并返回该 String 中出现次数最多的字符,但是如果程序员希望同时 返回最常出现的字符和 它出现的次数,那么程序设计就有点复杂了:或是创建一个包含字符及其出现次数的显式类,或将值作为字段保存到对象中并在需要时返回字段值。无论使用哪种方 法,与使用 Scala 相比,都需要编写大量代码;通过简单地返回包含字符及其出现次数的元组,Scala 不仅可以轻松地使用 “_1”、“_2” 等访问元组的各个值,还可以轻松地返回多个返回值。
如下节所示,Scala 频繁地将 Option 和元组保存到集合(例如 Array[T] 或列表)中,从而通过一个比较简单的结构提供了极大的灵活性和威力。
数组带
object ArrayExample1 { def main(args : Array[String]) : Unit = { for (i <- 0 to args.length-1) { System.out.println(args(i)) } } }
您走出阴霾
让我们重新审视一个老朋友 — 数组 — 在 Scala 中是 Array[T]。和 Java 代码中的数组一样,Scala 的 Array[T] 是一组有序的元素序列,使用表示数组位置的数值进行索引,并且该值不可以超过数组的总大小,如清单 4 所示:
尽管等同于 Java 代码中的数组(毕竟后者是最终的编译结果),Scala 中的数组使用了截然不同的定义。对于新手,Scala 中的数组实际上就是泛型类,没有增加 “内置” 状态(至少,不会比 Scala 库附带的其他类多)。例如,在 Scala 中,数组一般定义为 Array[T] 的实例,这个类定义了一些额外的有趣方法,包括常见的 “length” 方法,它将返回数组的长度。因此,在 Scala 中,可以按照传统意义使用 Array,例如使用 Int 在 0 到 args.length - 1 间进行迭代,并获取数组的第 i 个元素(使用圆括号而不是方括号来指定返回哪个元素,这是另一种名称比较有趣的方法)。
扩展数组
事实证明 Array 拥有大量方法,这些方法继承自一个非常庞大的 parent 层次结构:Array 扩展 Array0,后者扩展 ArrayLike[A],ArrayLike[A] 扩展 Mutable[A],Mutable[A] 又扩展 RandomAccessSeq[A],RandomAccessSeq[A] 扩展了 Seq[A],等等。实际上,这种层次结构意味着 Array 可以执行很多操作,因此与 Java 编程相比,在 Scala 中可以更轻松地使用数组。
例如,如清单 4 所示,使用 foreach 方法遍历数组更加简单并且更贴近函数的方式,这些都继承自 Iterable 特性:
object { def main(args : Array[String]) : Unit = { args.foreach( (arg) => System.out.println(arg) ) } }
看上去您没有节省多少工作,但是,将一个函数(匿名或其他)传入到另一个类中以便获得在特定语义下(在本例中指遍历数组)执行的能力,是函数编程的 常见主题。以这种方式使用更高阶函数并不局限于迭代;事实上,还得经常对数组内容执行一些过滤 操作去掉无用的内容,然后再处理结果。例如,在 Scala 中,可以轻松地使用 filter 方法进行过滤,然后获取结果列表并使用 map 和另一个函数(类型为 (T) => U,其中 T 和 U 都是泛型类型),或 foreach 来处理每个元素。我在清单 6 中采取了后一种方法(注意 filter 使用了一个 (T) : Boolean 方法,意味着使用数组持有的任意类型的参数,并返回一个 Boolean)。
清单 6. 查找所有 Scala 程序员
class ArrayTest { import org.junit._, Assert._ @Test def testFilter = { val programmers = Array( new Person("Ted", "Neward", 37, 50000, Array("C++", "Java", "Scala", "Groovy", "C#", "F#", "Ruby")), new Person("Amanda", "Laucher", 27, 45000, Array("C#", "F#", "Java", "Scala")), new Person("Luke", "Hoban", 32, 45000, Array("C#", "Visual Basic", "F#")), new Person("Scott", "Davis", 40, 50000, Array("Java", "Groovy")) ) // 查找所有Scala程序员 ... val scalaProgs = programmers.filter((p) => p.skills.contains("Scala") ) // 应该只有2 assertEquals(2, scalaProgs.length) // ... now perform an operation on each programmer in the resulting // array of Scala programmers (give them a raise, of course!) // scalaProgs.foreach((p) => p.salary += 5000) // Should each be increased by 5000 ... assertEquals(programmers(0).salary, 50000 + 5000) assertEquals(programmers(1).salary, 45000 + 5000) // ... except for our programmers who don't know Scala assertEquals(programmers(2).salary, 45000) assertEquals(programmers(3).salary, 50000) } }
创建一个新的 Array 时将用到 map 函数,保持原始的数组内容不变,实际上大多数函数性程序员都喜欢这种方式:
清单 7. Filter 和 map
@Test def testFilterAndMap = { val programmers = Array( new Person("Ted", "Neward", 37, 50000, Array("C++", "Java", "Scala", "C#", "F#", "Ruby")), new Person("Amanda", "Laucher", 27, 45000, Array("C#", "F#", "Java", "Scala")), new Person("Luke", "Hoban", 32, 45000, Array("C#", "Visual Basic", "F#")) new Person("Scott", "Davis", 40, 50000, Array("Java", "Groovy")) ) // Find all the Scala programmers ... val scalaProgs = programmers.filter((p) => p.skills.contains("Scala") ) // Should only be 2 assertEquals(2, scalaProgs.length) // ... now perform an operation on each programmer in the resulting // array of Scala programmers (give them a raise, of course!) // def raiseTheScalaProgrammer(p : Person) = { new Person(p.firstName, p.lastName, p.age, p.salary + 5000, p.skills) } val raisedScalaProgs = scalaProgs.map(raiseTheScalaProgrammer) assertEquals(2, raisedScalaProgs.length) assertEquals(50000 + 5000, raisedScalaProgs(0).salary) assertEquals(45000 + 5000, raisedScalaProgs(1).salary) }
注意,在清单 7 中,Person 的 salary 成员可以标记为 “val”,表示不可修改,而不是像上文一样为了修改不同程序员的薪资而标记为 “var”。
Scala 的 Array 提供了很多方法,在这里无法一一列出并演示。总的来说,在使用数组时,应该充分地利用 Array 提供的方法,而不是使用传统的 for ... 模式遍历数组并查找或执行需要的操作。最简单的实现方法通常是编写一个函数(如果有必要的话可以使用嵌套,如清单 7 中的 testFilterAndMap 示例所示),这个函数可以执行所需的操作,然后根据期望的结果将该函数传递给 Array 中的 map、filter、foreach 或其他方法之一。