Scala 专题教程-抽象成员(3): 初始化抽象vals

jerry Scala 2015年11月25日 收藏

抽象定义的vals在某些时候起到父类参数的角色,它们允许你在子类中提供最父类中省略的定义,这对于Trait来说尤其重要,因为Trait没有提供你可以传入参数的构造函数。因此为Trait提供参数支持通常是通过抽象vals来实现的。
例如我们之前例子中使用过的有理数类型,使用Trait方式定义如下:

  1. trait RationalTrait{
  2. val numerArg:Int
  3. val denomArg:Int
  4. }
  5.  

我们之前使用的Rational类定义定义了两个参数 n,d代表分子和分母。 这里我们使用RationalTrait 定义了两个抽象vals值, 为了构造这个Trait的一个实例,你需要提供这些抽象成员的实现,这里可以使用匿名类实例的方法构造一个实例:

  1. scala> val r= new RationalTrait {
  2. | val numerArg = 1
  3. | val denomArg = 2
  4. | }
  5. r: RationalTrait = $anon$1@341f55dd

这种构造RationalTrait 实例在形式上和之前的new Rational(1,2) 有点想像,但还是有些细节上的差别-在表达式初始化的顺序上的差异:
当你使用如下代码:

  1. new Rational(expr1,expr2)

其中的两个表达式expr1,expr2 在初始化类Rational之前就计算好了,因此在初始化Rational时,这些表达式是可以用的。而对于Trait来说,情况却相反:

  1. new RationalTrait{
  2. val numerArg = expr1
  3. val denomArg = expr2
  4. }

计算表达式expr1,expr2是和处理化匿名类实例的过程中进行的,而匿名类处理化是在RationalTrait之后进行的,因此在初始化RationalTrait时,这两个值是不可用的(或者是这两个值是缺省值0),这对于RationalTrait定义来说,不是个什么问题,因为RationalTrait的初始化没有使用到numerArg 和denomArg , 但对于下面的RationalTrait定义就存在问题了:

  1. trait RationalTrait{
  2. val numerArg :Int
  3. val denomArg :Int
  4. require(denomArg !=0)
  5. private val g = gcd(numerArg,denomArg)
  6. val numer = numerArg/g
  7. val denom = denomArg/g
  8. private def gcd(a:Int,b:Int):Int =
  9. if(b==0) a else gcd(b, a % b)
  10. override def toString = numer + "/" + denom
  11. }

如果此时,你使用某些表达式来构造这个Trait的实例,就会出问题了:

  1. scala> new RationalTrait {
  2. | val numerArg = x
  3. | val denomArg = 2 * x
  4. | }
  5. java.lang.IllegalArgumentException: requirement failed
  6. at scala.Predef$.require(Predef.scala:207)
  7. at RationalTrait$class.$init$(<console>:11)
  8. ... 39 elided
  9.  
  10. scala> new RationalTrait {
  11. | val numerArg = 1
  12. | val denomArg = 2
  13. | }
  14. java.lang.IllegalArgumentException: requirement failed
  15. at scala.Predef$.require(Predef.scala:207)
  16. at RationalTrait$class.$init$(<console>:11)
  17. ... 39 elided
  18.  

这是因为在执行RationalTrait 的初始化代码时denomArg 的值还是0,就抛出异常了。
由此你可以知道抽象Val值和类参数之间的不同,对于使用Trait的这个问题,Scala提供了两个解决方案:预先初始化的域和lazy vals。我们在下篇中介绍他们。