3.2. 操作(Operations)
Scala统一对象模型的另一个方面体现为每一个操作都是一个消息传递,也就是说是一个方法调用。例如:x与y相加操作x+y被解释为x.+y,也就是调用x这个对象的方法+,而y是该方法的参数。这种思想最早在Smalltalk中实现,在Scala中得到进一步改进,形成如下语法规约:首先,Scala将操作符作为普通标识符,也就是说,任何标识符或者以字母开头的一串字符、数字形成,或者以一串操作符形成。因此我们可以定义诸如+、<=、::等名称的方法。其次,Scala将任何两个表达式之间的标识符视为一个方法调用,例如:前述列表1当中的代码中,我们可以用(arg startsWith "-")作为语法糖(syntactic sugar)来替代默认的用法(arg.startsWith("-"))。
下面用一个例子来说明用户自定义操作符如何声明和使用:一个表示自然数的类Nat,它用Zero和Succ这两个类的实例来表示一个数字(当然很低效),每一个数字N用new SuccN(Zero)来表示。我们先定义一个抽象类来描述自然数所支持的所有操作。根据Nat的定义,自然数有两个抽象方法:isZero、pred,和三个具体方法:succ、+、-。
abstract class Nat {
def isZero: Boolean
def pred: Nat
def succ: Nat = new Succ(this)
def + (x: Nat): Nat =
if (x.isZero) this else succ + x.pred
def - (x: Nat): Nat =
if (x.isZero) this else pred -x.pred
}
注意Scala允许定义无参数方法,这种方法一旦名字被引用到即会调用,无需传递参数列表。另外,Scala类的抽象成员在语法上就通过没有定义来体现,无需添加abstract修饰符。
现在我们通过一个单例对象Zero和一个类Succ来扩展Nat,分别表示0和非0的自然数。
object Zero extends Nat {
def isZero: Boolean = true
def pred: Nat = throw new Error("Zero.pred")
override def toString: String = "Zero"
}
class Succ(n: Nat) extends Nat {
def isZero: Boolean = false
def pred: Nat = n
override def toString: String = "Succ("+n+")"
}
Succ类显示了Scala和Java的一些不同之处:Scala中类的构造函数紧接着类的名称出现,不需要在类的定义体中出现与类同名的构造函数。这样的构造函数称为主构造函数(primary constructor),当一个主构造函数因为对象实例化而被调用时,整个类定义被调用。另外还存在次构造函数的语法定义,用于需要不止一个构造函数的情况,参见[35]的第5.2.1节。
Zero对象和Succ类都实现了其父类Nat的两个抽象方法,同时还都覆盖了从Any继承来的toString方法。override关键字在覆盖被继承类的具体方法时是必须的,而用于实现父类中的抽象方法时则可以省略。这个操作符给出足够的冗余用来避免两类错误:一个是意外覆盖,即子类并不是有意覆盖父类中的方法,此时编译器将给出没有override操作符的错误信息。另一种类型的错误是覆盖路径中断,即父类方法参数变了,但没有修改子类对应方法,此时Scala编译器会给出没覆盖任何方法的错误信息,而不是自动将这个子类方法视为重载(overloading)。
允许用户自定义中缀(infix)操作符引出一个问题,即他们的优先级和结合性(precedence and associativity)。一个解决方案是像Haskell或SML那样在定义每一个操作符时可以给出“结合度”(fixity),但是这种方式与模块化编程之间不能很好交互。Scala采用一种相对简化的固定优先级与结合性的策略。每个中缀操作符由其第一个字符所决定,这与Java当中所有以非字母字符开头的操作符的优先级是一致的。下面是从低到高的操作符优先级:
(所有字母)
|
^
&
<、>
=、!
:
+、*
/、%
(所有其他特殊字符)
操作符一般是左结合的,x+y+z被解释为(x+y)+z,唯一的例外是以冒号(:)结尾的操作符是右结合的。一个例子是列表构造(list-consing)操作符“::”,xx::y::zs被解释为x::(y::zs)。右结合的操作符在方法方法查找上也是相反的,左结合操作符以其左方对象作为消息接收者,右结合操作符当然以右方对象为消息接收者。例如:x::y::zs被视作zs.::(y).::(x)。实际上,::是Scala的List类的一个方法,他将该方法参数对应的列表添加在接收消息的对象对应的列表的前面,并将合并成的新列表作为结果返回。