因为Scala致力于帮助你充分利用函数式和指令式风格两方面的好处,它的集合类型库于是就区分了集合类的可变和不可变。例如,数组始终是可变的,而列表始终不可变。当问题讨论到集和映射,Scala同样提供了可变和不可变的替代品,不过用了不同的办法。对于集和映射,Scala把可变性建模在类继承中。
51CTO编辑推荐:Scala编程语言专题
例如,Scala的API包含了集的一个基本特质:trait,特质这个概念接近于Java的接口。Scala于是提供了两个子特质,一个是可变的集,另一个是不可变的集。就如你在图3.2里会看到的,这三个特质都共享同样的简化名,Set。然而它们的全称不一样,因为每个都放在不同的包里。Scala的API里具体的Set类,如图3.2的HashSet类,扩展了要么是可变的,要么不可变的Set特质。(尽管Java里面称为“实现”了接口,在Scala里面称为“扩展”或“混入”了特质。)因此,如果你想要使用HashSet,你可以根据你的需要选择可变的或不可变的变体。创造集的缺省方法展示在代码3.5中:
- var jetSet = Set("Boeing", "Airbus")
- jetSet += "Lear"
- println(jetSet.contains("Cessna"))
代码 3.5 创造,初始化,和使用不可变集
代码3.5的第一行代码里,定义了名为jetSet的新var,并使用了包含两个字串,"Boeing"和"Airbus"的不可变集完成了初始化。就像例子中展示的,Scala中创建集的方法与创建列表和数组的类似:通过调用Set伴生对象的名为apply的工厂方法。代码3.5中,对scala.collection.immutable.Set的伴生对象调用了apply方法,返回了一个缺省的,不可变Set的实例。Scala编译器推断jetSet的类型为不可变Set[String]。
要向集加入新的变量,可以在集上调用+,传入新的元素。可变的和不可变的集都提供了+方法,但它们的行为不同。可变集将把元素加入自身,不可变集将创建并返回一个包含了添加元素的新集。代码3.5中,你使用的是不可变集,因此+调用将产生一个全新集。因此尽管可变集提供的实际上是+=方法,不可变集却不是。本例中,代码的第二行,“jetSet += "Lear"”,实质上是下面写法的简写:
- jetSetjetSet = jetSet + "Lear"
因此在代码3.5的第二行,你用一个包含了"Boeing","Airbus"和"Lear"的新集重新赋值了jetSet这个var。最终,代码3.5的最后一行打印输出了集是否包含字串"Cessna"。(正如你所料到的,输出false。)
如果你需要不可变集,就需要使用一个引用:import,如代码3.6所示:
- import scala.collection.mutable.Set
- val movieSet = Set("Hitch", "Poltergeist")
- movieSet += "Shrek"
- println(movieSet)
代码 3.6 创建,初始化,和使用可变集
代码3.6的第一行里引用了可变Set。就像Java那样,引用语句允许你使用简单名,如Set,以替代更长的,全标识名。结果,当你在第三行写Set的时候,编译器就知道你是指scala.collection.mutable.Set。在那行里,你使用包含字串"Hitch"和"Poltergeist"的新可变集初始化了movieSet。下一行通过在集上调用+=方法向集添加了"Shrek"。正如前面提到的,+=是实际定义在可变集上的方法。如果你想的话,你可以替换掉movieSet += "Shrek"的写法,写成movieSet.+=("Shrek")。
尽管目前为止看到的通过可变和不可变的Set工厂方法制造的缺省的集实现很可能能够满足极大多数的情况,但偶尔你也或许想要个显式的集类。幸运的是,语法是相同的。只要引用你需要的类,并使用它伴生对象的工厂方法即可。例如,如果你需要一个不可变的HashSet,你可以这么做:
- import scala.collection.immutable.HashSet
- val hashSet = HashSet("Tomatoes", "Chilies")
- println(hashSet + "Coriander")
Map是Scala里另一种有用的集合类。和集一样,Scala采用了类继承机制提供了可变的和不可变的两种版本的Map,你能在图3.3里看到,Map的类继承机制看上去和Set的很像。scala.collection包里面有一个基础Map特质和两个子特质Map:可变的Map在scala.collection.mutable里,不可变的在scala.collection.immutable里。
Map的实现,如显示在类继承图3.3里的HashMap,扩展了要么可变,要么不可变特质。你可以使用与那些用在数组,列表和集中的一样的工厂方法去创造和初始化映射。例如,代码3.7展示了可变映射的创造过程:
- import scala.collection.mutable.Map
- val treasureMap = Map[Int, String]()
- treasureMap += (1 -> "Go to island.")
- treasureMap += (2 -> "Find big X on ground.")
- treasureMap += (3 -> "Dig.")
- println(treasureMap(2))
代码 3.7 创造,初始化,和使用可变映射
代码3.7的第一行里,你引用了可变形式的Map。然后就定义了一个叫做treasureMap的val并使用空的包含整数键和字串值的可变Map初始化它。映射为空是因为你没有向工厂方法传递任何值(“Map[Int, String]()”的括号里面是空的)。 下面的三行里你使用->和+=方法把键/值对添加到Map里。像前面例子里演示的那样,Scala编译器把如1 -> "Go to island"这样的二元操作符表达式转换为(1).->("Go to island.")。因此,当你输入1 -> "Go to island.",你实际上是在值为1的Int上调用->方法,并传入值为"Go to island."的String。这个->方法可以调用Scala程序里的任何对象,并返回一个包含键和值的二元元组。 然后你在把这个元组传递给treasureMap指向的Map的+=方法。最终,最后一行输出打印了treasureMap中的与键2有关的值。如果你执行这段代码,将会打印:
- Find big X on ground.
如果你更喜欢不可变映射,就不用引用任何类了,因为不可变映射是缺省的,代码3.8展示了这个例子:
- val romanNumeral = Map(
- 1 -> "I", 2 -> "II", 3 -> "III", 4 -> "IV", 5 -> "V"
- )
- println(romanNumeral(4))
代码 3.8 创造,初始化,和使用不可变映射
由于没有引用,当你在代码3.8的第一行里提及Map时,你会得到缺省的映射:scala.collection.immutable.Map。传给工厂方法入五个键/值元组,返回包含这些传入的键/值对的不可变Map。如果你执行代码3.8中的代码,将会打印输出IV。
本文节选自《Programming in Scala》
【相关阅读】