本篇繼續(xù)上一篇,前面我們定義了 Rational 的主構(gòu)造函數(shù),并檢查了輸入不允許分母為 0。下面我們就可以開始實(shí)行兩個(gè) Rational 對(duì)象相加的操作。我們需要實(shí)現(xiàn)的函數(shù)化對(duì)象,因此 Rational 的加法操作應(yīng)該是返回一個(gè)新的 Rational 對(duì)象,而不是返回被相加的對(duì)象本身。我們很可能寫出如下的實(shí)現(xiàn):
class Rational (n:Int, d:Int) {
require(d!=0)
override def toString = n + "/" +d
def add(that:Rational) : Rational =
new Rational(n*that.d + that.n*d,d*that.d)
}
實(shí)際上編譯器會(huì)給出如下編譯錯(cuò)誤:
<console>:11: error: value d is not a member of Rational
new Rational(n*that.d + that.n*d,d*that.d)
^
<console>:11: error: value d is not a member of Rational
new Rational(n*that.d + that.n*d,d*that.d)
這是為什么呢?盡管類參數(shù)在新定義的函數(shù)的訪問(wèn)范圍之內(nèi),但僅限于定義類的方法本身(比如之前定義的 toString 方法,可以直接訪問(wèn)類參數(shù)),但對(duì)于 that 來(lái)說(shuō),無(wú)法使用 that.d 來(lái)訪問(wèn) d. 因?yàn)?that 不在定義的類可以訪問(wèn)的范圍之內(nèi)。此時(shí)需要定類的成員變量。(注:后面定義的 case class 類型編譯器自動(dòng)把類參數(shù)定義為類的屬性,這是可以使用 that.d 等來(lái)訪問(wèn)類參數(shù))。
修改 Rational 定義,使用成員變量定義如下:
class Rational (n:Int, d:Int) {
require(d!=0)
val number =n
val denom =d
override def toString = number + "/" +denom
def add(that:Rational) =
new Rational(
number * that.denom + that.number* denom,
denom * that.denom
)
}
要注意的我們這里定義成員變量都使用了 val ,因?yàn)槲覀儗?shí)現(xiàn)的是“immutable”類型的類定義。number 和 denom 以及 add 都可以不定義類型,Scala 編譯能夠根據(jù)上下文推算出它們的類型。
scala> val oneHalf=new Rational(1,2)
oneHalf: Rational = 1/2
scala> val twoThirds=new Rational(2,3)
twoThirds: Rational = 2/3
scala> oneHalf add twoThirds
res0: Rational = 7/6
scala> oneHalf.number
res1: Int = 1
可以看到,這是就可以使用 .number 等來(lái)訪問(wèn)類的成員變量。
Scala 也使用 this 來(lái)引用當(dāng)前對(duì)象本身,一般來(lái)說(shuō)訪問(wèn)類成員時(shí)無(wú)需使用 this ,比如實(shí)現(xiàn)一個(gè) lessThan 方法,下面兩個(gè)實(shí)現(xiàn)是等效的。
def lessThan(that:Rational) =
this.number * that.denom < that.number * this.denom
和
def lessThan(that:Rational) =
number * that.denom < that.number * denom
但如果需要引用對(duì)象自身,this 就無(wú)法省略,比如下面實(shí)現(xiàn)一個(gè)返回兩個(gè) Rational 中比較大的一個(gè)值的一個(gè)實(shí)現(xiàn):
def max(that:Rational) =
if(lessThan(that)) that else this
其中的 this 就無(wú)法省略。
在定義類時(shí),很多時(shí)候需要定義多個(gè)構(gòu)造函數(shù),在 Scala 中,除主構(gòu)造函數(shù)之外的構(gòu)造函數(shù)都稱為輔助構(gòu)造函數(shù)(或是從構(gòu)造函數(shù)),比如對(duì)于 Rational 類來(lái)說(shuō),如果定義一個(gè)整數(shù),就沒(méi)有必要指明分母,此時(shí)只要整數(shù)本身就可以定義這個(gè)有理數(shù)。我們可以為 Rational 定義一個(gè)輔助構(gòu)造函數(shù),Scala 定義輔助構(gòu)造函數(shù)使用 this(…)的語(yǔ)法,所有輔助構(gòu)造函數(shù)名稱為 this。
def this(n:Int) = this(n,1)
所有 Scala 的輔助構(gòu)造函數(shù)的第一個(gè)語(yǔ)句都為調(diào)用其它構(gòu)造函數(shù),也就是 this(…),被調(diào)用的構(gòu)造函數(shù)可以是主構(gòu)造函數(shù)或是其它構(gòu)造函數(shù)(最終會(huì)調(diào)用主構(gòu)造函數(shù)),這樣使得每個(gè)構(gòu)造函數(shù)最終都會(huì)調(diào)用主構(gòu)造函數(shù),從而使得主構(gòu)造函數(shù)稱為創(chuàng)建類單一入口點(diǎn)。在 Scala 中也只有主構(gòu)造函數(shù)才能調(diào)用基類的構(gòu)造函數(shù),這種限制有它的優(yōu)點(diǎn),使得 Scala 構(gòu)造函數(shù)更加簡(jiǎn)潔和提高一致性。
Scala 類定義私有成員的方法也是使用 private 修飾符,為了實(shí)現(xiàn) Rational 的規(guī)范化顯示,我們需要使用一個(gè)求分子和分母的最大公倍數(shù)的私有方法 gcd。同時(shí)我們使用一個(gè)私有變量 g 來(lái)保存最大公倍數(shù),修改 Rational 的定義:
scala> class Rational (n:Int, d:Int) {
| require(d!=0)
| private val g =gcd (n.abs,d.abs)
| val number =n/g
| val denom =d/g
| override def toString = number + "/" +denom
| def add(that:Rational) =
| new Rational(
| number * that.denom + that.number* denom,
| denom * that.denom
| )
| def this(n:Int) = this(n,1)
| private def gcd(a:Int,b:Int):Int =
| if(b==0) a else gcd(b, a % b)
| }
defined class Rational
scala> new Rational ( 66,42)
res0: Rational = 11/7
注意 gcd 的定義,因?yàn)樗莻€(gè)回溯函數(shù),必須定義返回值類型。Scala 會(huì)根據(jù)成員變量出現(xiàn)的順序依次初始化它們,因此g必須出現(xiàn)在 number 和 denom 之前。
更多建議: