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