5.4 操作符即方法

Scala给它的基础类型提供了一组丰富的操作符。前面的章节也提到过,这些操作符实际上只是普通方法调用的漂亮语法。例如,1 + 2实际上与1.+(2)是等同的。换句话说,Int类包含了一个名称为+的方法,接收一个Int参数,返回Int类型的结果。这个+方法是在你对两个Int值做加法时执行的:

要验证这一点,可以用方法调用的形式显式地写出这个表达式:

事实上,Int类包含了多个重载overloaded)的+方法,分别接收不同的参数类型。[4]例如,Int类还有另一个也叫作+的方法,接收一个Long参数,返回一个Long类型的结果。如果你对一个Int参数加上一个Long参数,则后一个+方法会被调用,例如:

+符号是一个操作符(更确切地说,它是一个中缀操作符)。操作符表示法并不局限于那些在其他语言中看上去像操作符的方法。可以在操作符表示法中使用任何方法。[5]例如,String类有一个indexOf方法,接收一个Char参数。这个indexOf方法可以检索字符串中给定字符首次出现的位置,返回位置下标,如果没有找到,则返回-1。可以像使用操作符那样使用indexOf方法:

任何方法都可以是操作符

在Scala中,操作符并不是特殊的语法,任何方法都可以是操作符。是否让方法成为操作符取决于你如何“用”它。当你写下“s.indexOf('o')”时,indexOf并不是操作符;但当你写下“s indexOf 'o'”时,indexOf就是操作符了,因为你用的是操作符表示法。

至此,你已经看到了中缀操作符表示法的若干示例。中缀操作符表示法意味着被调用的方法名称位于对象和你想传入的参数中间,如“7 + 2”。Scala还提供了两种操作符表示法:前缀和后缀。在前缀表示法中,需要将方法名放在要调用的方法的对象前面(如-7中的'-')。在后缀表示法中,需要将方法名放在对象之后(如“7 toLong”中的“toLong”)[6]

与中缀操作符表示法(操作符接收两个操作元,一个在左一个在右)不同,前缀和后缀操作符是一元的unary):它们只接收一个操作元。在前缀表示法中,操作元位于操作符的右侧。前缀操作符的例子有-2.0!found~0xFF等。与中缀操作符类似,这些前缀操作符也是调用方法的一种简写。不同的是,方法名称是“unary_”加上操作符。举例来说,Scala会把-2.0这样的表达式转换成如下的方法调用:“(2.0).unary_-”。你可以自己演示一下,先后用操作符表示法和显式方法调用来完成:

唯一能被用作前缀操作符的是+-!~。因此,如果你定义了一个名称为unary_!的方法,则可以对满足类型要求的值或变量使用前缀操作符表示法,如!p。不过,如果你定义了一个名称为unary_*的方法,就不能用前缀操作符表示法了,因为*并不是可以被用作前缀操作符的4个标识符之一。可以像正常的方法调用那样调用p.unary_*方法,但如果你尝试用*p这样的方式来调用,则Scala会将其当作*.p来解析,这大概并不是你想要的效果。[7]

后缀操作符是那些不接收参数且在调用时没有用英文句点、圆括号的方法。在Scala中,可以在方法调用时省去空的圆括号。从约定俗成的角度来讲,在方法有副作用时,需要保留空的圆括号,如println();而在方法没有副作用时,则可以省去这组圆括号,如对String调用toLowerCase方法时:

在后一种不带参数的场景(无副作用)下,可以选择省去句点,使用后缀操作符表示法。不过,编译器会要求先引入scala.language. postfixOps,才能以操作符表示法来调用某个方法:

在本例中,toLowerCase被当作后缀操作符作用在了操作元s上。

综上所述,要了解Scala基础类型支持的操作符,只需要在Scala API文档中查看对应类型声明的方法。不过,由于这是一本Scala教程,我们将在接下来的几节中快速地带你过一遍这些方法中的大多数。

Java程序员的快速通道

本章剩余部分讲到的Scala知识点与Java中的是一致的。如果你是Java高手且时间有限,可以安心地跳过,直接进入5.8节,这一节会介绍Scala与Java在对象相等性方面的不同。