3.6 运算符重载
从技术的角度来看,Scala没有运算符,提及“运算符重载”时,指的是重载像+
,+-
等这样的符号。在Scala里,这些实际上是方法名:运算符利用了Scala灵活的方式调用语法——在Scala里,对象引用和方法名之间的点(.
)不是必需的。
这两个特性给了我们运算符重载的错觉。这样,调用ref1 + ref2
,实际上写的是ref1.+(ref2)
,这是在调用ref1.
的+()
方法。看个+
运算符的例子,来自Complex
类,这个类表示复数⑥:
⑥复数有实部和虚部,它们对于计算复杂方程式很有用,比如负数的平方根。
ScalaForTheJavaEyes/Complex.scala
class Complex(val real: Int, val imaginary: Int) {
def +(operand: Complex) : Complex = {
new Complex(real + operand.real, imaginary + operand.imaginary)
}
override def toString() : String = {
real + (if (imaginary < 0) "" else "+") + imaginary + "i"
}
}
val c1 = new Complex(1, 2)
val c2 = new Complex(2, -3)
val sum = c1 + c2
println("(" + c1+ ")+ (" + c2 + ")=" + sum)
如果执行上面的代码会看到:
(1+2i) + (2-3i) = 3-1i
在第一个语句中,创建了一个名为Complex
的类,定义一个构造函数,接收两个参数。在4.1节中,我们会看到如何用Scala富有表现力的语法创建类。
在+
方法里创建了一个新的Complex
类实例。结果的实部和虚部分别对应两个运算数实部和虚部的和。语句c1 + c2
会变成一个方法调用,以c2
为实参调用c1
的+()
方法,也就是c1.+(c2)
。
我们讨论了Scala对运算符重载简单而优雅的支持。不过,Scala没有运算符,这个事实也许会让人有点头痛。或许,你会对运算符优先级感到困惑。Scala没有运算符,所以它无法定义运算符优先级,对吗?恐怕不是,因为24 - 2 + 3 * 6在Java和Scala里都等于40。Scala确实没有定义运算符优先级,但它定义了方法的优先级。
方法名的第一个字符决定了它的优先级⑦。如果表达式里有两个具有相同优先级的字符,那么左边的运算符优先级更高。下面从低到高列出了首字符的优先级⑧:
⑦如果方法名以冒号(
:
)结尾,Scala会调换方法调用的参数;参见8.4节,“方法名转换”。⑧参见附录A。
所有字母
|
^
&
< >
= !
:
+ -
* / %
所有其他特殊字符
我们看个运算符/方法优先级的例子。下面的代码里,我们为Complex
既定义了加法方法,又定义了乘法方法:
ScalaForTheJavaEyes/Complex2.scala
class Complex(val real: Int, val imaginary: Int) {
def +(operand: Complex) : Complex = {
println("Calling +")
new Complex(real + operand.real, imaginary + operand.imaginary)
}
def *(operand: Complex) : Complex = {
println("Calling *")
new Complex(real * operand.real - imaginary * operand.imaginary,
real * operand.imaginary + imaginary * operand.real)
}
override def toString() : String = {
real + (if (imaginary < 0) "" else "+") + imaginary + "i"
}
}
val c1 = new Complex(1, 4)
val c2 = new Complex(2, -3)
val c3 = new Complex(2, 2)
println(c1 + c2 * c3)
调用*()
前,会先调用了在左边的+()
,但是因为*()
优先,它会先执行,如下所示:
Calling *
Calling +
11+2i