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

第7步 用类型参数化数组

在Scala中,可以用new来实例化对象或类的实例。当你用Scala实例化对象时,可以用值和类型来对其进行参数化parameterize)。参数化的意思是在创建实例时对实例做“配置”。可以用值来参数化一个实例,做法是在构造方法的括号中传入对象参数。例如,如下Scala代码将实例化一个新的java.math.BigInteger,并用值"12345"对它进行参数化:

也可以用类型来参数化一个实例,做法是在方括号里给出一个或多个类型。比如代码示例3.1。在这个示例中,greetStrings是一个类型为Array[string]的值(一个“字符串的数组”),它被初始化成长度为3的数组,因为我们在代码的第一行用3这个值对它进行了参数化。如果以脚本的方式运行示例3.1,会看到另一个Hello, world!问候语。注意当你同时用类型和值来参数化一个实例时,先是用方括号包起来的类型(参数),然后才是用圆括号包起来的值(参数)。

示例3.1 用类型参数化一个数组

注意

虽然示例3.1展示了重要的概念,这并不是Scala创建并初始化数组的推荐做法。你将在示例3.2中看到更好的方式。

如果你想更明确地表达你的意图,也可以显式地给出greetStrings的类型:

由于Scala的类型推断,这行代码在语义上跟示例3.1的第一行完全一致。不过从这样的写法当中可以看到,类型参数(方括号包起来的类型名称)是该实例类型的一部分,但值参数(用圆括号包起来的值)并不是。greetString的类型是Array[String],而不是Array[String](3)

示例3.1中接下来的三行分别初始化了greetString数组的各个元素:

正如我们前面提到的,Scala的数组的访问方式是将下标放在圆括号里,而不是像Java那样用方括号。所以该数组的第0个元素是greetString(0)而不是greetString[0]

这三行代码也展示了Scala关于val的一个重要概念。当你用val定义一个变量时,变量本身不能被重新赋值,但它指向的那个对象是有可能发生改变的。在本例中,不能将greetStrings重新赋值成另一个数组,greetString永远指向那个跟初始化时相同的Array[String]实例。不过“可以”改变那个Array[String]的元素,因此数组本身是可变的(mutable)。

示例3.1的最后两行代码包括一个for表达式,作用是将greetStrings数组中的各个元素依次打印出来:

这个for表达式的第一行展示了Scala的另一个通行的规则:如果一个方法只接收一个参数,在调用它的时候,可以不使用英文句点或圆括号。本例中的to实际上是接收一个Int参数的方法。代码0 to2会被转换为(0).to(2)[1]注意这种方式仅在显式地给出方法调用的目标对象时才有效。不能写“println 10”,但可以写“Console println 10”。

Scala从技术上讲并没有操作符重载(operator overloading),因为它实际上并没有传统意义上的操作符。类似+、-、*、/这样的字符可以被用作方法名。因此,当你在之前的第1步往Scala解释器中键入1 + 2时,实际上是调用了Int对象1上名为+的方法,将2作为参数传入。如图3.1所示,也可以用更传统的方法调用方式来写1 + 2这段代码:(1).+(2)

图3.1 Scala中所有操作都是方法调用

本例展示的另一个重要理念是为什么Scala用圆括号(而不用方括号)来访问数组。跟Java相比Scala的特例更少。数组不过是类的实例,这一点跟其他Scala实例没有本质区别。当你用一组圆括号将一个或多个值包起来,并将其应用(apply)到某个对象时,Scala会将这段代码转换成对这个对象的一个名为apply的方法的调用。所以greetStrings(i)会被转换成greetStrings.apply(i)。因此,在Scala中访问一个数组的元素就是一个简单的方法调用,跟其他方法调用一样。当然,这样的代码仅在对象的类型实际上定义了apply方法时才能编译通过。因此,这并不是一个特例,这是一个通行的规则。

同理,当我们尝试对通过圆括号应用了一个或多个参数的变量进行赋值时,编译器会将代码转换成对update方法的调用,这个update方法接收两个参数:用圆括号括起来的值,以及等号右边的对象。例如:

会被转换成:

因此,如下代码在语义上跟示例3.1是等同的:

Scala将从数组到表达式的一切都当作带有方法的对象来处理,由此来实现概念上的简单化。不需要记住各种特例,比如Java中基本类型与它们对应的包装类型的区别,或数组和常规对象的区别等。不仅如此,这种统一并不带来显著的性能开销。Scala在编译代码时,会尽可能使用Java数组、基本类型和原生的算术指令。

到此为止,看到的代码示例都可以正常地编译和运行,但是Scala还提供了一种比通常做法更精简的方式来创建和初始化数组。参看示例3.2,这段代码会创建一个长度为3的新数组,并用传入的字符串"zero""one""two"初始化。由于你传给它的是字符串,编译器推断出数组的类型为Array[String]

示例3.2 创建并初始化一个数组

在示例3.2中,实际上是调用了一个名为apply的工厂方法,这个方法创建并返回了新的数组。这个apply方法接收一个变长的参数列表[2],该方法定义在Array伴生对象companion object)中。你将会在4.3节了解到更多关于伴生对象的内容。如果你是个Java程序员,可以把这段代码想象成是调用了Array类的一个名为apply的静态方法。同样是调用apply方法但是更啰唆的写法如下: