特质 (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 中这个类型是所有其他类型的父类型。因为它同时也是基本类型如IntFloat 的父类型,可以将其视为更为一般化的 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 特质。

然后我们重新定义继承自Objectequals 函数好让这个类可以正确的根据每个属性来比较日期。因为在 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. }

这个函数使用了预定义函数isInstanceOfasInstanceOfisInstanceOf 对应到 Java 的instanceof 运算子,只在当使用它的对象之类型跟给定类型一样时传回真。asInstanceOf 对应到 Java 的转型运算子,如果对象是给定类型的实例,该对象就会被视为给定类型,不然就会丢出ClassCastException

最后我们需要定义测试小于的谓词如下。

  1. def <(that: Any): Boolean = {
  2. if (!that.isInstanceOf[Date])
  3. error("cannot compare " + that + " and a Date")
  4. val o = that.asInstanceOf[Date]
  5. (year < o.year) ||
  6. (year == o.year && (month < o.month ||
  7. (month == o.month && day < o.day)))
  8. }

这边使用了另外一个预定义函数error,它会丢出带着给定错误信息的例外。这便完成了Date 类。这个类的实例可被视为日期或是可比较对象。而且它们通通都定义了之前所提到的六个比较谓词:equals< 直接出现在类定义当中,其他则是继承自Ord 特质。

特质在其他场合也有用,不过详细探讨它们的用途并不在本文件目标内。