Scala伴生類和伴生對象

Scala伴生類和伴生對象

單例對象與類同名時,這個單例對象被稱爲這個類的伴生對象,而這個類被稱爲這個單例對象的伴生類。伴生類和伴生對象要在同一個源文件中定義,伴生對象和伴生類可以互相訪問其私有成員。不與伴生類同名的單例對象稱爲孤立對象。

看看例子:

import scala.collection.mutable.Map

class ChecksumAccumulator {
  private var sum = 0
  def add(b: Byte) {
    sum += b
  }
  def checksum(): Int = ~(sum & 0xFF) + 1
}

object ChecksumAccumulator {
  private val cache = Map[String, Int]()
  def calculate(s: String): Int =
    if (cache.contains(s))
    cache(s)
  else {
      val acc = new ChecksumAccumulator
      for (c <- s)
        acc.add(c.toByte)
      val cs = acc.checksum()
      cache += (s -> cs)
      println("s:"+s+" cs:"+cs)
      cs
    }

  def main(args: Array[String]) {
    println("Java 1:"+calculate("Java"))
    println("Java 2:"+calculate("Java"))
    println("Scala :"+calculate("Scala"))
  }
}

ChecksumAccumulator單例對象有一個方法,calculate,用來計算所帶的String參數中字符的校驗和。它還有一個私有字段,cache,一個緩存之前計算過的校驗和的可變映射。2方法的第一行,“if (cache.contains(s))”,檢查緩存,看看是否傳遞進來的字串已經作爲鍵存在於映射當中。如果是,就僅僅返回映射的值,“cache(s)”。否則,執行else子句,計算校驗和。else子句的第一行定義了一個叫acc的val並用新建的ChecksumAccumulator實例初始化它。下一行是個for表達式,對傳入字串的每個字符循環一次,並在其上調用toByte把字符轉換成Byte,然後傳遞給acc所指的ChecksumAccumulator實例的add方法。完成了for表達式後,下一行的方法在acc上調用checksum,獲得傳入字串的校驗和,並存入叫做cs的val。下一行,“cache += (s -> cs)”,傳入的字串鍵映射到整數的校驗和值,並把這個鍵-值對加入cache映射。方法的最後一個表達式,“cs”,保證了校驗和爲此方法的結果。

這裏打印的結果是:

s:Java cs:-130
Java 1:-130
Java 2:-130
s:Scala cs:-228
Scala :-228

問題來了,ChecksumAccumulator單例對象是不能new的,但是在代碼中出現了val acc = new ChecksumAccumulator,這不是矛盾嗎?其實不然,這裏new的其實是ChecksumAccumulator單例對象的伴生類,即ChecksumAccumulator類,而伴生類和伴生對象可以互相訪問對方的私有成員,所以acc可以訪問ChecksumAccumulator單例對象的cache變量。同理,那麼第一次測試“Java”字符串的時候,acc實例執行了checksum()方法,接下來並把這個數據存到cache這個val中。在第二次測試“Java”字符串的時候,程序是直接從cache變量中獲取到了數據,並返回。

這裏也可以看出類和單例對象的一個差別是,單例對象是在第一次訪問的時候初始化,不可以new,不能帶參數,而類可以new,可以帶參數。每個單例對象都被作爲由一個靜態變量指向的虛構類:synthetic class的一個實例來實現,因此它們與Java靜態類有着相同的初始化語法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章