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