抽象定義的 vals 在某些時候起到父類參數(shù)的角色,它們允許你在子類中提供最父類中省略的定義,這對于 Trait 來說尤其重要,因為 Trait 沒有提供你可以傳入?yún)?shù)的構(gòu)造函數(shù)。因此為 Trait 提供參數(shù)支持通常是通過抽象 vals 來實現(xiàn)的。
例如我們之前例子中使用過的有理數(shù)類型,使用 Trait 方式定義如下:
trait RationalTrait{
val numerArg:Int
val denomArg:Int
}
我們之前使用的 Rational 類定義定義了兩個參數(shù) n,d 代表分子和分母。 這里我們使用 RationalTrait 定義了兩個抽象 vals 值, 為了構(gòu)造這個 Trait 的一個實例,你需要提供這些抽象成員的實現(xiàn),這里可以使用匿名類實例的方法構(gòu)造一個實例:
scala> val r= new RationalTrait {
| val numerArg = 1
| val denomArg = 2
| }
r: RationalTrait = $anon$1@341f55dd
這種構(gòu)造 RationalTrait 實例在形式上和之前的 new Rational(1,2) 有點想像,但還是有些細(xì)節(jié)上的差別-在表達(dá)式初始化的順序上的差異: 當(dāng)你使用如下代碼:
new Rational(expr1,expr2)
其中的兩個表達(dá)式 expr1,expr2 在初始化類 Rational 之前就計算好了,因此在初始化 Rational 時,這些表達(dá)式是可以用的。而對于 Trait 來說,情況卻相反:
new RationalTrait{
val numerArg = expr1
val denomArg = expr2
}
計算表達(dá)式 expr1,expr2 是和處理化匿名類實例的過程中進(jìn)行的,而匿名類處理化是在 RationalTrait 之后進(jìn)行的,因此在初始化 RationalTrait 時,這兩個值是不可用的(或者是這兩個值是缺省值0),這對于 RationalTrait 定義來說,不是個什么問題,因為 RationalTrait 的初始化沒有使用到 numerArg 和 denomArg , 但對于下面的 RationalTrait 定義就存在問題了:
trait RationalTrait{
val numerArg :Int
val denomArg :Int
require(denomArg !=0)
private val g = gcd(numerArg,denomArg)
val numer = numerArg/g
val denom = denomArg/g
private def gcd(a:Int,b:Int):Int =
if(b==0) a else gcd(b, a % b)
override def toString = numer + "/" + denom
}
如果此時,你使用某些表達(dá)式來構(gòu)造這 個Trait 的實例,就會出問題了:
scala> new RationalTrait {
| val numerArg = x
| val denomArg = 2 * x
| }
java.lang.IllegalArgumentException: requirement failed
at scala.Predef$.require(Predef.scala:207)
at RationalTrait$class.$init$(<console>:11)
... 39 elided
scala> new RationalTrait {
| val numerArg = 1
| val denomArg = 2
| }
java.lang.IllegalArgumentException: requirement failed
at scala.Predef$.require(Predef.scala:207)
at RationalTrait$class.$init$(<console>:11)
... 39 elided
這是因為在執(zhí)行 RationalTrait 的初始化代碼時 denomArg 的值還是0,就拋出異常了。 由此你可以知道抽象 Val 值和類參數(shù)之間的不同,對于使用 Trait 的這個問題,Scala 提供了兩個解決方案:預(yù)先初始化的域和 lazy vals。我們在下篇中介紹他們。