第7步 用类型参数化数组
在Scala中,可以用new来实例化对象或类的实例。当你用Scala实例化对象时,可以用值和类型对其进行参数化。参数化的意思是在创建实例时对实例做“配置”。可以用值来参数化一个实例,做法是在构造方法的括号中传入对象参数。例如,如下Scala代码将实例化一个新的java.math.BigInteger并用值"12345"对它进行参数化:
也可以用类型来参数化一个实例,做法是在方括号里给出一个或多个类型,如代码示例3.1所示。在这个示例中,greetStrings是一个类型为Array[String]的值(一个“字符串的数组”),它被初始化成长度为3的数组,因为我们在代码的第一行用3这个值对它进行了参数化。如果以脚本的方式运行示例3.1,则会看到另一句Hello, world!问候语。注意,当你同时用类型和值来参数化一个实例时,先使用方括号括起来的类型(参数),再使用圆括号括起来的值(参数)。
示例3.1 用类型参数化一个实例
注意
虽然示例3.1展示了重要的概念,但这并不是Scala创建并初始化数组的推荐做法。你将在示例3.2(39页)中看到更好的方式。
如果你想更明确地表达你的意图,也可以显式地给出greetStrings的类型:
由于Scala的类型推断,这行代码在语义上与示例3.1的第一行完全一致。不过从这样的写法中可以看到,类型参数(方括号括起来的类型名称)是该实例类型的一部分,但值参数(圆括号括起来的值)并不是。greetStrings的类型是Array[String],而不是Array[String](3)。
示例3.1中接下来的3行分别初始化了greetStrings数组的各个元素:
正如前面提到的,Scala的数组的访问方式是将下标放在圆括号里,而不是像Java那样用方括号。所以该数组的第0个元素是greetStrings(0)而不是greetStrings[0]。
这3行代码也展示了Scala关于val的一个重要概念。当用val定义一个变量时,变量本身不能被重新赋值,但它指向的那个对象是有可能发生改变的。在本例中,不能将greetStrings重新赋值成另一个数组,greetStrings永远指向那个与初始化时相同的Array[String]实例。不过你“可以”改变那个Array[String]的元素,因此数组本身是可变的。
示例3.1的最后两行代码包括一个for表达式,其作用是将greetStrings数组中的各个元素依次打印出来:
这个for表达式的第一行展示了Scala的另一个通行的规则:如果一个方法只接收一个参数,则在调用它的时候,可以不使用英文句点或圆括号。本例中的to实际上是接收一个Int参数的方法。代码0 to 2会被转换成(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方法但是更啰唆的写法如下: