類似於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方法。上面寫的這些代碼,在這個類被初始化時由主構造方法執行。