Scala编程(第4版)
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

第10步 使用集和映射

由于Scala想让你同时享有函数式和指令式编程风格的优势,其集合类库特意对可变和不可变的集合进行了区分。举例来说,数组永远是可变的,列表永远是不可变的。Scala同时还提供了集(set)和映射(map)的可变和不可变的不同选择,但使用同样的简单名字。对于集和映射而言,Scala通过不同的类继承关系来区分可变和不可变版本。

例如,Scala的API包含了一个基础的特质trait)来表示集,这里的特质跟Java的接口定义类似(将在第12章了解到更多关于特质的内容)。在此基础上,Scala提供了两个子特质(subtrait),一个用于表示可变集,另一个用于表示不可变集。

在图3.2中可以看到,这三个特质都叫作Set。不过它们的完整名称并不相同,因为它们分别位于不同的包。Scala API中具体用于表示集的类,比如图3.2中的HashSet类,分别扩展自可变或不可变的特质Set(在Java中“实现”某个接口,而在Scala中“扩展”或者“混入”特质)。因此,如果想要使用一个HashSet,可以根据需要选择可变或不可变的版本。创建集的默认方式如示例3.5所示:

示例3.5 创建、初始化并使用一个不可变集

图3.2 Scala集的类继承关系

在示例3.5的第一行,定义了一个新的名为jetSetvar,将其初始化成一个包含两个字符串,"Boeing""Airbus"的不可变集。如这段代码所示,在Scala中可以像创建列表和数组那样创建集:通过调用Set伴生对象的名为apply的工厂方法。在示例3.5中,实际上调用了scala.collection.immutable.Set的伴生对象的apply方法,返回一个默认的、不可变的Set的对象。Scala编译器推断出jetSet的类型为不可变的Set[String]

要向不可变集添加新元素,可以对集调用+方法,传入这个新元素,+方法会创建并返回一个新的包含了新元素的不可变集。虽然可变集提供了一个实际的+=方法,但不可变集并不直接提供这个方法。

本例的第二行,“jetSet += "Linear"”,本质上是如下代码的简写:

因此,在示例3.5的第二行,实际上是将jetSet这个var重新赋值成了一个包含"Boeing""Aribus""Linear"的新集。示例3.5的最后一行打印出这个集是否包含"Cessna"。(正如你预期的那样,它将打印false)。

如果你想要的是一个可变集,需要做一次引入import),如示例3.6所示:

示例3.6 创建、初始化并使用一个可变集

示例3.6的第一行引入了scala.collection.mutable。import语句让你在代码中使用简单名字,比如Set,而不是更长的完整名。这样一来,当你在第三行用到mutable.Set的时候,编译器知道你指的是scala.collection.mutable.Set。在那一行,将movieSet初始化成一个新的包含字符串"Hitch""Poltergeist"的新的可变集。接下来的一行通过调用集的+=方法将"Shrek"添加到可变集里。前面我们提到过,+=实际上是一个定义在可变集上的方法。只要你想,也完全可以不用movieSet += "Shrek"这样的写法,而是写成movieSet.+=("Shrek")[6]

尽管由可变和不可变Set的工厂方法生产出来的默认集的实现对于大多数情况来说都够用了,偶尔可能也需要一类特定的集。幸运的是,语法上面并没有大的不同。只需要简单地引入你需要的类,然后使用其伴生对象上的工厂方法即可。例如,如果需要一个不可变的HashSet,可以:

Scala的另一个有用的集合类是Map。跟集类似,Scala也提供了Map的可变和不可变的版本,用类继承关系来区分。如图3.3所示,映射(map)的类继承关系跟集的类继承关系很像。在scala.collection包里有一个基础的Map特质,还有两个子特质,都叫Map,可变的那个位于scala.collection.mutable,而不可变的那个位于scala.collection.immutable

图3.3 Scala映射的类继承关系

Map的实现,比如图3.3中的HashMap,扩展自可变或不可变的特质。跟数组、列表和集类似,可以使用工厂方法来创建和初始化映射。

示例3.7 创建、初始化并使用一个可变的映射

示例3.7展示了一个可变映射的具体例子。在示例3.7的第一行,引入了可变的Map特质。接下来定义了一个名为treasureMapval,并初始化成一个空的,包含整数键和字符串值的可变Map。这个映射之所以是空的,是因为没有向工厂方法传入任何内容(在代码“Map[Int, String]()”中圆括号是空的)。[7]在接下来的几行,通过->和+=方法向映射添加键值对(key/value pair)。正如我们前面演示过的,Scala编译器会将二元(binary)的操作,比如1 -> "Go to island.",转换成标准的方法调用,即(1).->("Go to island.")。因此,当你写1-> "Go to island."时,实际上是对这个值为1的整数调用->方法,传入字符串"Go to island."。可以在Scala的任何对象上调用这个->方法,它将返回包含键和值两个元素的元组。[8]然后将这个元组传给treasureMap指向的那个映射对象的+=方法。最后一行将打印出treasureMap中键2对应的值。

运行这段代码,它将打印:

如果你更倾向于使用不可变的映射,则不需要任何引入,因为默认的映射就是不可变的。见示例3.8:

示例3.8 创建、初始化并使用一个不可变映射

由于没有显式引入,当你在示例3.8中的第一行提到Map时,得到的是默认的那个scala.collection.immutable.Map。接下来将五组键值元组传给映射的工厂方法,返回一个包含了传入的键值对的不可变Map。如果运行示例3.8中的代码,它将打印出“IV”。