學(xué)習(xí)Scala類的定義,字段和方法
類是對(duì)象的藍(lán)圖。一旦你定義了類,你就可以用關(guān)鍵字new從類的藍(lán)圖里創(chuàng)建對(duì)象。比方說(shuō),如果給出了類的定義:
51CTO編輯推薦:Scala編程語(yǔ)言專題
- class ChecksumAccumulator {
- // class definition goes here
- }
你就能創(chuàng)建ChecksumAccumulator對(duì)象:
- new CheckSumAccumulator
類定義里,可以放置字段和方法,這些被籠統(tǒng)地稱為成員:member。字段,不管是用val或是用var定義的,都是指向?qū)ο蟮淖兞?。方法,用def定義,包含了可執(zhí)行的代碼。字段保留了對(duì)象的狀態(tài)或者數(shù)據(jù),而方法使用這些數(shù)據(jù)對(duì)對(duì)象做運(yùn)算工作。當(dāng)你實(shí)例化類的時(shí)候,執(zhí)行期環(huán)境會(huì)設(shè)定一些內(nèi)存來(lái)保留對(duì)象狀態(tài)的鏡像——也就是說(shuō),變量的內(nèi)容。舉例來(lái)說(shuō),如果你定義了ChecksumAccumulator類并給它一個(gè)叫做sum的var字段:
- class ChecksumAccumulator {
- var sum = 0
- }
并實(shí)例化兩次:
- val acc = new ChecksumAccumulator
- val csa = new ChecksumAccumulator
對(duì)象在內(nèi)存里的鏡像看上去大概是這樣的:
由于在類ChecksumAccumulator里面定義的字段sum是var,而不是val,你之后可以重新賦值給它不同的Int值,如:
- acc.sum = 3
現(xiàn)在,圖像看上去會(huì)變成:
這張圖里***件要注意的事情是這里有兩個(gè)sum變量,一個(gè)在acc指向的對(duì)象里,另一個(gè)在csa指向的對(duì)象里。字段的另一種說(shuō)法是實(shí)例變量:instance variable,因?yàn)槊恳粋€(gè)實(shí)例都有自己的變量集??傮w來(lái)說(shuō),對(duì)象實(shí)例的變量組成了對(duì)象的內(nèi)存鏡像。你不僅可以因?yàn)榭吹絻蓚€(gè)sum變量來(lái)體會(huì)關(guān)于這個(gè)的演示,同樣可以通過(guò)改變其中一個(gè)時(shí),另一個(gè)不變來(lái)發(fā)現(xiàn)這點(diǎn)。
本例中另外一件需要注意的事情是,盡管acc是val,你仍可以改變acc指向的對(duì)象。你對(duì)acc(或csa)不能做的事情是由于它們是val,而不是var,你不可以把它們?cè)俅钨x值為不同的對(duì)象。例如,下面的嘗試將會(huì)失?。?/P>
- // 編譯不過(guò),因?yàn)閍cc是val
- acc = new ChecksumAccumulator
于是你可以總結(jié)出來(lái),acc將永遠(yuǎn)指向初始化時(shí)指向的同一個(gè)ChecksumAccumulator對(duì)象,但是包含于對(duì)象中的字段可以隨時(shí)改動(dòng)。
想讓對(duì)象具有魯棒性的一個(gè)重要的方法就是保證對(duì)象的狀態(tài)——實(shí)例變量的值——在對(duì)象整個(gè)生命周期中持續(xù)有效。***步就是通過(guò)把字段變?yōu)?EM>私有的:private去阻止外界直接對(duì)它的訪問(wèn),因?yàn)樗接凶侄沃荒鼙欢x在同一個(gè)類里的方法訪問(wèn),所有能更新字段的代碼將被鎖定在類里。要聲明字段是私有的,可以把訪問(wèn)修飾符private放在字段的前面,就像這樣:
- class ChecksumAccumulator {
- private var sum = 0
- }
有了這個(gè)ChecksumAccumulator的定義,任何從類外部訪問(wèn)sum的嘗試都會(huì)失?。?BR>
- val acc = new ChecksumAccumulator
- acc.sum = 5 //編譯不過(guò),因?yàn)閟um是私有的
注意
在Scala里把成員公開(kāi)的方法是不顯式地指定任何訪問(wèn)修飾符。換句話說(shuō),你在Java里要寫上“public”的地方,在Scala里只要什么都不要寫就成。Public是Scala的缺省訪問(wèn)級(jí)別。
現(xiàn)在sum是私有的,所以唯一能訪問(wèn)sum的代碼是定義在類自己里面的。這樣,除非我們定義什么方法,否則ChecksumAccumulator對(duì)任何人都沒(méi)什么用處:
- class ChecksumAccumulator {
- private var sum = 0
- def add(b: Byte): Unit = {
- sum += b
- }
- def checksum(): Int = {
- return ~(sum & 0xFF) + 1
- }
- }
現(xiàn)在ChecksumAccumulator有兩個(gè)方法了,add和checksum,兩個(gè)都以基本的的函數(shù)定義方式展示,參見(jiàn)第38頁(yè)的圖2.1。
傳遞給方法的任何參數(shù)都可以在方法內(nèi)部使用。Scala里方法參數(shù)的一個(gè)重要特征是它們都是val,不是var。參數(shù)是val的理由是val更容易講清楚。你不需要多看代碼以確定是否val被重新賦值,而var則不然。 如果你想在方法里面給參數(shù)重新賦值,結(jié)果是編譯失?。?BR>
- def add(b: Byte): Unit = {
- b += 1 // 編譯不過(guò),因?yàn)閎是val
- sum += b
- }
盡管在這個(gè)ChecksumAccumulator版本里的add和checksum方法正確地實(shí)現(xiàn)了預(yù)期的功能,你還是可以用更簡(jiǎn)潔的風(fēng)格表達(dá)它們。首先,checksum方法***的return語(yǔ)句是多余的可以去掉。如果沒(méi)有發(fā)現(xiàn)任何顯式的返回語(yǔ)句,Scala方法將返回方法中***一個(gè)計(jì)算得到的值。
對(duì)于方法來(lái)說(shuō)推薦的風(fēng)格實(shí)際是避免顯式的尤其是多個(gè)返回語(yǔ)句。代之以把每個(gè)方法當(dāng)作是創(chuàng)建返回值的表達(dá)式。這種哲學(xué)將鼓勵(lì)你制造很小的方法,把較大的方法分解為多個(gè)更小的方法。另一方面,設(shè)計(jì)選擇取決于設(shè)計(jì)內(nèi)容,Scala使得編寫具有多個(gè),顯式的return的方法變得容易,如果那的確是你期望的。
因?yàn)閏hecksum要做的只有計(jì)算值,不需要return。所以這個(gè)方法的另一種簡(jiǎn)寫方式是,假如某個(gè)方法僅計(jì)算單個(gè)結(jié)果表達(dá)式,則可以去掉大括號(hào)。如果結(jié)果表達(dá)式很短,甚至可以把它放在def同一行里。這樣改動(dòng)之后,類ChecksumAccumulator看上去像這樣:
- class ChecksumAccumulator {
- private var sum = 0
- def add(b: Byte): Unit = sum += b
- def checksum(): Int = ~(sum & 0xFF) + 1
- }
像ChecksumAccumulator的add方法那樣的結(jié)果類型為Unit的方法,執(zhí)行的目的就是它的副作用。通常我們定義副作用為在方法外部某處改變狀態(tài)或者執(zhí)行I/O活動(dòng)。比方說(shuō),在add這個(gè)例子里,副作用就是sum被重新賦值了。表達(dá)這個(gè)方法的另一種方式是去掉結(jié)果類型和等號(hào),把方法體放在大括號(hào)里。這種形式下,方法看上去很像過(guò)程:procedure,一種僅為了副作用而執(zhí)行的方法。代碼4.1的add方法里演示了這種風(fēng)格:
- // 文件ChecksumAccumulator.scala
- class ChecksumAccumulator {
- private var sum = 0
- def add(b: Byte) { sum += b }
- def checksum(): Int = ~(sum & 0xFF) + 1
- }
代碼 4.1 類ChecksumAccumulator的最終版
應(yīng)該注意到令人困惑的地方是當(dāng)你去掉方法體前面的等號(hào)時(shí),它的結(jié)果類型將注定是Unit。不論方法體里面包含什么都不例外,因?yàn)镾cala編譯器可以把任何類型轉(zhuǎn)換為Unit。例如,如果方法的***結(jié)果是String,但方法的結(jié)果類型被聲明為Unit,那么String將被轉(zhuǎn)變?yōu)閁nit并失去它的值。下面是這個(gè)例子:
- scala> def f(): Unit = "this String gets lost"
- f: ()Unit
例子里,String被轉(zhuǎn)變?yōu)閁nit因?yàn)閁nit是函數(shù)f聲明的結(jié)果類型。Scala編譯器會(huì)把一個(gè)以過(guò)程風(fēng)格定義的方法,就是說(shuō),帶有大括號(hào)但沒(méi)有等號(hào)的,在本質(zhì)上當(dāng)作是顯式定義結(jié)果類型為Unit的方法。例如:
- scala> def g() { "this String gets lost too" }
- g: ()Unit
因此,如果你本想返回一個(gè)非Unit的值,卻忘記了等號(hào)時(shí),那么困惑就出現(xiàn)了。所以為了得到你想要的結(jié)果,你需要插入等號(hào):
- scala> def h() = { "this String gets returned!" }
- h: ()java.lang.String
- scala> h
- res0: java.lang.String = this String gets returned!
【相關(guān)閱讀】
- 學(xué)習(xí)Scala腳本:從文件里讀取行記錄
- 學(xué)習(xí)識(shí)別Scala的函數(shù)式風(fēng)格
- Scala編程實(shí)例:使用Set和Map
- Scala編程實(shí)例:使用List和Tuple
- Scala編程實(shí)例:帶類型的參數(shù)化數(shù)組
【責(zé)任編輯:王苑 TEL:(010)68476606】