一、抽象成员
类可以用“abstract”修饰变成抽象的,特质天生就是抽象的,所以抽象类和特质里可以包含抽象成员,也就是没有完整定义的成员。Scala有四种抽象成员:抽象val字段、抽象var字段、抽象方法和抽象类型,它们的声明形式如下:
trait Abstract {
type T // 抽象类型
def transform(x: T): T // 抽象方法
val initial: T // 抽象val字段
var current: T // 抽象var字段
}
因为定义不充分,存在不可初始化的字段和类型,或者没有函数体的方法,所以抽象类和特质不能直接用new构造实例。抽象成员的本意,就是让更具体的子类或子对象来实现它们。例如:
class Concrete extends Abstract {
type T = String
def transform(x: String) = x + x
val initial = "hi"
var current = initial
}
抽象类型指的是用type关键字声明的一种类型——它是某个类或特质的成员但并未给出定义。虽然类和特质都定义了一种类型,并且它们可以是抽象的,但这不意味着抽象类或特质就叫抽象类型,抽象类型永远都是类和特质的成员。在使用抽象类型进行定义的地方,最后都要被解读成抽象类型的具体定义。而使用抽象类型的原因,一是给名字冗长或含义不明的类型起一个别名,二是声明子类必须实现的抽象类型。
在不知道某个字段正确的值,但是明确地知道在当前类的每个实例中,该字段都会有一个不可变更的值时,就可以使用抽象val字段。抽象val字段与抽象无参方法类似,而且访问方式完全一样。但是,抽象val字段保证每次使用时都返回一个相同的值,而抽象方法的具体实现可能每次都返回不同的值。另外,抽象val字段只能实现成具体的val字段,不能改成var字段或无参方法;而抽象无参方法可以实现成具体的无参方法,也可以是val字段。
抽象var字段与抽象val字段类似,但是是一个可被重新赋值的字段。与前一章讲解的具体var字段类似,抽象var字段会被编译器隐式地展开成抽象setter和抽象getter方法,但是不会在当前抽象类或特质中生成一个“private[this] var”字段。这个字段会在定义了其具体实现的子类或子对象当中生成。例如:
trait AbstractTime {
var hour: Int
var minute: Int
}
// 相当于
trait AbstractTime {
def hour: Int // hour的getter方法
def hour_=(x: Int) // hour的setter方法
def minute: Int // minute的getter方法
def minute_=(x: Int) // minute的setter方法
}
二、初始化抽象val字段
抽象val字段有时会承担超类参数的职能:它们允许程序员在子类中提供那些在超类中缺失的细节。这对特质尤其重要,因为特质没有构造方法,参数化通常都是通过子类实现抽象val字段来完成。例如:
trait RationalTrait {
val numerArg: Int
val denomArg: Int
}
要在具体的类中混入这个特质,就必须实现它的两个抽象val字段。例如:
new RationalTrait {
val numerArg = 1
val denomArg = 2
}
注意,前面说过,这不是直接实例化特质,而是隐式地用一个匿名类混入了该特质,并且花括号里的内容属于隐式的匿名类。
构造子类的实例对象时,首先构造超类/超特质的组件,然后才轮到子类的剩余组件。因为花括号里的内容不属于超类/超特质,所以在构造超类/超特质的组件时,花括号里的内容其实是无用的。并且在这个过程中,如果需要访问超类/超特质的抽象val字段,会交出相应类型的默认值(比如Int类型的默认值是0),而不是花括号里的定义。只有轮到构造子类的剩余组件时,花括号里的子类定义才会派上用场。所以,在构造超类/超特质的组件时,尤其是特质还不能接收子类的参数,如果默认值不满足某些要求,构造就会出错。例如:
scala> trait RationalTrait {
| val numerArg: Int
| val denomArg: Int
| require(denomArg != 0)
| }
defined trait RationalTraitscala> new RationalTrait {
| val numerArg = 1
| val denomArg = 2
| }
java.lang.IllegalArgumentException: requirement failed
at scala.Predef$.require(Predef.scala:264)
at RationalTrait.$init$(<console>:14)
... 32 elided