特质 (Traits)
除了由父类继承行为以外,Scala 类还可以从一或多个特质导入行为。
对一个 Java 程序员最简单去理解特质的方式应该是视它们为带有实例的接口。在 Scala 里,当一个类继承特质时,它实现了该特质的接口并继承所有特质带有的功能。
为了理解特质的用处,让我们看一个经典示例:有序对象。大部分情况下,一个类所产生出来的对象之间可以互相比较大小是很有用的,如排序它们。在Java里可比较大小的对象实作Comparable
介面。在Scala中借由定义等价于Comparable
的特质Ord
,我们可以做的比Java稍微好一点。
当在比较对象的大小时,有六个有用且不同的谓词 (predicate):小于、小于等于、等于、不等于、大于等于、大于。但是把六个全部都实现很烦,尤其是当其中有四个可以用剩下两个表示的时候。也就是说,(举例来说) 只要有等于跟小于谓词,我们就可以表示其他四个。在 Scala 中这些观察可以很漂亮的用下面的特质声明呈现:
trait Ord {
def < (that: Any): Boolean
def <=(that: Any): Boolean = (this < that) || (this == that)
def > (that: Any): Boolean = !(this <= that)
def >=(that: Any): Boolean = !(this < that)
}
这份定义同时创造了一个叫做Ord
的新类型,跟 Java 的Comparable
接口有着同样定位,且给了一份以第一个抽象谓词表示剩下三个谓词的预设实作。因为所有对象预设都有一份等于跟不等于的谓词,这边便没有定义。
上面使用了一个Any
类型,在 Scala 中这个类型是所有其他类型的父类型。因为它同时也是基本类型如Int
、Float
的父类型,可以将其视为更为一般化的 JavaObject
类型。
因此只要定义测试相等性跟小于的谓词,并且加入Ord
,就可以让一个类的对象们互相比较大小。让我们实作一个表示阳历日期的Date
类来做为例子。这种日期是由日、月、年组成,我们将用整数来表示这三个资料。因此我们可以定义Date
类为:
class Date(y: Int, m: Int, d: Int) extends Ord {
def year = y
def month = m
def day = d
override def toString(): String = year + "-" + month + "-" + day
这边要注意的是声明在类名称跟参数之后的extends Ord
。这个语法声明了Date
继承Ord
特质。
然后我们重新定义继承自Object
的equals
函数好让这个类可以正确的根据每个属性来比较日期。因为在 Java 中equals
直接比较实际对象本身,并不能在这边用。于是我们有下面的例子:
override def equals(that: Any): Boolean =
that.isInstanceOf[Date] && {
val o = that.asInstanceOf[Date]
o.day == day && o.month == month && o.year == year
}
这个函数使用了预定义函数isInstanceOf
跟asInstanceOf
。isInstanceOf
对应到 Java 的instanceof
运算子,只在当使用它的对象之类型跟给定类型一样时传回真。asInstanceOf
对应到 Java 的转型运算子,如果对象是给定类型的实例,该对象就会被视为给定类型,不然就会丢出ClassCastException
。
最后我们需要定义测试小于的谓词如下。
def <(that: Any): Boolean = {
if (!that.isInstanceOf[Date])
error("cannot compare " + that + " and a Date")
val o = that.asInstanceOf[Date]
(year < o.year) ||
(year == o.year && (month < o.month ||
(month == o.month && day < o.day)))
}
这边使用了另外一个预定义函数error
,它会丢出带着给定错误信息的例外。这便完成了Date
类。这个类的实例可被视为日期或是可比较对象。而且它们通通都定义了之前所提到的六个比较谓词:equals
跟<
直接出现在类定义当中,其他则是继承自Ord
特质。
特质在其他场合也有用,不过详细探讨它们的用途并不在本文件目标内。