Programming In Scala筆記-第四章、類和對象

  類似於Java,Scala中也有類和對象的概念。

一、類、屬性和方法

1、類
  類是對一類事物的抽象,當一個類被定義後,就可以以該定義爲模板,定義該類的一系列對象。比如說有以下一個模板

人類:
  有姓名;
  有一個大腦;
  有四肢;
  有性別;
  會思考;
  有語言能力;

  上面這個模板可以認爲是對正常人類的一個最簡單的抽象,那麼有兩個人張三和李四,就是人類這一模板的具體實例了。

  在Scala中,用class關鍵字定義一個類,用new關鍵字實例化該類的一個對象。一個簡單的類定義和實例化如下所示

class ChecksumAccumulator {
  // 具體定義代碼省略
}

new ChecksumAccumulator

  在上面代碼中省略的部分可以寫入一些該類的屬性和方法。所謂屬性是指該類的一些特性,比如上面例子中的姓名,性別等,都可以認爲是人類的一個屬性。而方法是指該類可以執行的一些邏輯片段,比如上面例子中定義人類的走路方式爲站立行走,那麼正常的兩個人類張三和李四的行走方式都是站立行走了。
  有關類的屬性和方法,接下來再進行分析。

2、屬性
  屬性可以用var和val來定義,比如
  

class ChecksumAccumulator {
  var sum = 0
}

  那麼,使用下面代碼new出兩個對象  

val acc = new ChecksumAccumulator
val csa = new ChecksumAccumulator

  acc和csa都有一個屬性sum。並且值都是0。如下圖所示,前面兩個圓圈表示acc和csa對象的具體存儲,指向同一個0是由於0在常量池中只有一個。
  這裏寫圖片描述
  執行acc.sum=3可以將acc的sum屬性更新爲3,那麼現在的情況是這樣的
  這裏寫圖片描述
  
  從這裏可以看出,雖然acc是以val來定義的,但是可以修改其中的屬性值。定義爲val的作用是,如果將acc指向另一個ChecksumAccumulator對象時就會報錯了。一般來說,最好將屬性定義爲private類型,private類型的屬性,只能由該類中的方法進行訪問和修改,儘量避免外部代碼直接修改對象的屬性值。如果需要屬性發生變化,可以通過方法來實現。

3、方法
  方法以def開頭,裏面包含了一段可執行的代碼片段。這裏需要注意的是,方法的參數是val類型的,如果在代碼中更改參數值,是會報錯的。 

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

  上面代碼中定義了兩個方法,根據這兩個方法的定義,可以進行一定的簡化,

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

  上一篇博客中提到過,返回值爲Unit的語句,會產生side effects。這裏add方法的side effects是改變了sum變量的值。side effects產生的影響,要麼是改變了某個mutable變量的值,要麼是產生了一些I/O,這些並不會影響其他代碼的執行。
  對於這種返回值爲Unit的方法,可以去掉前面的Unit =部分,將函數體用花括號包起來。

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

  上面代碼中add方法的寫法,即使其執行邏輯的返回值不爲Unit,這種寫法也顯示的將該方法返回值設置爲Unit了。

二、語句劃分

  不知道有沒有發現在上面的代碼中並不像Java代碼那樣,每一句代碼的結尾用分號進行分割。在Scala中每一行語句的結尾並不需要寫一個分號,一般情況下,以換行作爲默認的分隔符。只有當一行中寫多句代碼時,代碼之間才需要以分號進行分隔。

val s = "hello"; println(s)

  Scala默認換行爲代碼直接的分隔符,

x
+ y

會被認爲是兩行代碼,x和+ y。但是如果確實同一句代碼需要換行應該怎麼辦?可以用圓括號將同一句代碼包圍起來,在圓括號中可以使用換行。

(x
+ y)

其實就是x + y的換行形式。

三、單例對象

  雖然Scala是函數式編程語言,但是說到面向對象,其實Scala比Java還嚴格。在Java中,一個類還可以有一些靜態屬性或者靜態方法,這樣在不new一個對象時,也可以使用其中很多的靜態屬性和方法。但是Scala沒有靜態屬性和方法一說,任何屬性和方法的使用,都是通過對象來進行的。
  如果確實需要在Scala中直接使用類名調用屬性和方法應該怎麼做?Scala通過提供一種單例對象,或者說伴生對象巧妙的實現了這一功能。前面我們定義了一個ChecksumAccumulator類,我們用另一個關鍵字object,同樣使用ChecksumAccumularot這個名字,就生成了一個ChecksumAccumulator類的伴生對象了。

import scala.collection.mutable.Map
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)
    cs
  }
}

接下來我們就可以使用ChecksumAccumulator.calculate直接進行計算了。從字面上看,和Java中調用靜態方法是一樣的。
  上面的ChecksumAccumulator對象稱爲ChecksumAccumulator類的伴生對象,ChecksumAccumulator類稱爲ChecksumAccumulator對象的伴生類。伴生對象和伴生類,需要寫在同一個源文件中,並且伴生類和伴生對象之間可以互相訪問對方的private成員。

四、代碼運行的入口

  在Java中,一個Application是以某個Class中的public static void main(String[] args)的方法開始的。想要運行一段Scala代碼,需要定義一個單例對象,並且在該單例對象中寫一個main方法,這個方法接收的參數是一個String類型數組,返回值類型爲Unit。

import ChecksumAccumulator.calculate
object Summer {
  def main(args: Array[String]) {
    for (arg <- args)
      println(arg +": "+ calculate(arg))
  }
}

  一個object中的main方法,是一個Application的入口。類似於java中的main方法。
  
  不過Scala也提供了一個名爲Application的trait,可以簡單的把trait理解成Java中的接口。如果object繼承自該trait的話,那麼不需要main方法也能運行起來。把原來需要寫在main方法中的代碼,直接寫在隨後的花括號中。

import ChecksumAccumulator.calculate
object FallWinterSpringSummer extends Application {
  for (season <- List("fall", "winter", "spring"))
    println(season + " : " + calculate(season))
}

這段代碼之所以能夠運行,從Application中可以看到,這裏面已經實現了一個main方法。上面寫的這些代碼,在這個類被初始化時由主構造方法執行。

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