1. 程式人生 > >Scala類與對象

Scala類與對象

規範 分享圖片 barney unit 級別 ... 嵌套類 member 都是

類簡介

簡介

類是對象的藍圖。一旦你定義了類,就可以用關鍵字new根據類的藍圖創建對象。在類的定義裏,可以放置字段和方法,這些被籠統地稱為成員。對於字段,不管是val還是var定義的,都是指向對象的變量。對於方法,用def定義,包含了可執行代碼。字段保留了對象的狀態或數據,而方法使用這些數據執行對象的運算工作。當類被實例化的時候,運行時環境會預留一些內存來保留對象的狀態映像——即變量的內容。

示例

創建類示例:

class SumAccumulator {
    var sum = 0
}

然後實例化兩次:

val acc: SumAccumulator = new
SumAccumulator val csa: SumAccumulator = new SumAccumulator

這時內存裏對象的狀態映像如下:

技術分享圖片

由於字段sum是var類型而非val,所以sum可以被賦予新的Int類型的值:例如

acc.sum = 3

此時映像會變成:

技術分享圖片

Scala類的重要特性

訪問修飾符

  • Public是Scala默認的訪問級別

為了保證對象的健壯性,可以把類中字段變為私有的(private)以阻止外界直接對它訪問。因為私有字段只能被定義成在同一類裏的方法訪問,所有跟新字段的代碼將鎖定在類裏。要聲明字段是私有的,可以把訪問修飾符private放在字段的前面。

代碼示例:

class SumAccumulator {
    private var sum = 0
} 

使用private修飾後,任何從類外部對sum的訪問都將失敗

val acc: SumAccumulator = new SumAccumulator
acc.sum = 3 //編譯不通過,因為sum是私有的

方法

1. Scala方法參數類型

Scala方法參數中的類型都是val而不是var類型

def add(a: Int, b: Int): Int = {
    a = a + b //編譯錯誤:Reassignment to val (就是對val類型賦值的錯誤)
a }   

2. Scala方法中return

Scala方法中的return可以去掉,從而簡化代碼

代碼示例:

def numSum(num:Int):Int ={
    val sum = num + 10
    sum //省略return關鍵字,簡化代碼
 
}

此時返回值就是sum

3. Scala方法中的“=”號

Scala方法中的“=”號非常重要:因為Scala編譯器可以把任何類型轉換為Unit如果定義的函數中”=”號忘記了添加,那麽Scala會默認它返回值為Unit類型。若你本來想返回一個String類型的值,由於沒有添加”=”,String類型返回值會被轉換為Unit類型而丟棄掉。

代碼示例:

def printStr() {
    "這是String類型的返回值"
}

返回結果為: ()

正確代碼示例:

def printStr(): String = {
    "這是String類型的返回值"
}

返回結果為: 這是String類型的返回值

4. Scala方法表達式

假如某個方法僅計算單個結果的表達式,這可以去掉花括號,如果結果表達式很短,甚至可以把它放在def的同一行裏。

代碼示例:

def numSum(num:Int):Int = num + 10

5. Scala中分號推斷

scala程序中,語句末尾的分號通常是可選的,若一行僅有一個語句也可以不加分號。不過,如果一行包含多條語句時,分號則是必須的。

不加分號:

  1. if(x < 2)
  2. println("too small")
  3. else
  4. println("ok")

必須加分號:

val x = 10; println(x)  //兩個語句,必須加分號



Scala通常的風格是把操作符放在行尾而不是行頭:

錯誤示例:

  1. val x = 10;
  2. val y = 3
  3. val z = x //它會被編譯器識別為z = x ; +y 兩個語句
  4. +y

打印結果:

10

正確示例:

  1. val x = 10;
  2. val y = 3
  3. val z = x +
  4. y
  5. println(z)

打印結果:

13



Scala分號推斷規則:

  1. 疑問行由一個不能合法作為語句結尾的字結束,如句點或中綴操作符。
  2. 下一行開始於不能作為語句開始的詞。
  3. 行結束於括號(...)或方框[...]內部,因為這些符號不能容納多個語句。

6. Scala無參方法

調用Scala類中無參方法時,可以寫上括號,也可以不寫。對於類中的取值器來說,去掉()是不錯的風格。

代碼示例:

  1. object exam1 {
  2. def main(args: Array[String]): Unit = {
  3. val per: Person = new Person
  4. per.talk() //ok
  5. per.talk //同樣ok
  6. }
  7. }
  8. class Person {
  9. def talk(): Unit = println("Talking")
  10. }



如果你想強制使用這種風格,可以在聲明方法時不帶()

代碼示例:

  1. per.talk() //此時這種寫法是錯誤的
  2. per.talk //OK的
  3. class Person {
  4. def talk = println("Talking")
  5. }

Scala類中getter和setter

getter和setter

Scala類中使用公有字段的話,任何人都可以修改這個字段,使得安全性大大降低。所以我們更傾向於使用getter和setter方法:

Scala對類中每個字段都提供了getter和setter方法,分別叫做age和age_=,

代碼示例:

  1. val per: Person = new Person
  2. per.age = 18
  3. class Person {
  4. var age = 0
  5. }

如果想查看這些方法,可以先編譯Person類,然後用javap查看字節碼:

技術分享圖片

你可以自己重新定義getter和setter方法

代碼示例:

  1. object exam1 {
  2. def main(args: Array[String]): Unit = {
  3. val per: Person = new Person
  4. per.age = 18
  5. per.age = 16 //由於在setter裏面控制了age不能變小,所以執行結果age不會變
  6. println(per.age)
  7. per.age = 19 //使用setter,賦予大於原來的age
  8. println(per.age)
  9. }
  10. }
  11. class Person {
  12. private var privateAge = 0 //變成私有變量並改名
  13. def age = privateAge
  14. def age_=(newAge: Int) { // age_= 不能有空格
  15. if (newAge > privateAge) privateAge = newAge //使得年齡不能變小
  16. }
  17. }

打印結果為:

  1. 18
  2. 19



Scala中每個字段生成getter和setter的限制:

  1. 如果字段是私有的,則getter和setter方法也是私有的。
  2. 如果字段是val,則只有getter方法被生成。
  3. 如果你不需要任何getter或setter,可以將字段聲明為private[this]

Scala在實現類中屬性時的四個選擇:

  1. 自動生成一個getter和setter。
  2. 自動生成一個getter。
  3. 自定義foo和foo_=方法。
  4. 自定義foo方法。

Bean屬性

JavaBean規範把Java屬性定義為一堆getFoo和setFoo方法。類似於Java,當你將Scala字段標註為 @BeanProperty時,getter和setter方法會自動生成。

代碼示例:

  1. import scala.beans.BeanProperty
  2. object exam1 {
  3. def main(args: Array[String]): Unit = {
  4. val per: Person = new Person
  5. per.name = "zhagnsan"
  6. per.setName("lisi") //BeanProperty生成的setName方法
  7. println(per.getName) //BeanProperty生成的getName方法
  8. }
  9. }
  10. class Person {
  11. @BeanProperty var name:String = _
  12. }

打印結果為:

Lisi

上述類Person中由@BeanProperty生成了四個方法:

  1. 1. name: String
  2. 2. name_= (newValue: String): Unit
  3. 3. getName(): String
  4. 4. setName (newValue: String): Unit



圖示為針對字段生成的方法:

技術分享圖片

Scala類中構造器

和java或C++一樣,Scala也可以有任意多的構造器。不過,Scala類有一個構造器比其他所有構造器都更為重要,它就是主構造器。除了主構造器外,Scala類還可以有任意多的輔助構造器。

輔助構造器

Scala中輔助構造器和Java或C++十分相似,只有兩處不同:

  1. 輔助構造器的名稱為this。
  2. 每一個輔助構造器都必須以一個對先前已定義的其他輔助構造器或主構造器的調用開始

這裏有一個帶有兩個輔助構造器的類。

代碼示例:

  1. object exam1 {
  2. def main(args: Array[String]): Unit = {
  3. val per1: Person = new Person //主構造器
  4. val per2: Person = new Person("Bob") //第一個輔助構造器
  5. val per3: Person = new Person("Bob",18) //第二個輔助構造器
  6. }
  7. }
  8. class Person {
  9. private var name = ""
  10. private var age = 0
  11. //一個輔助構造器
  12. def this(name: String) {
  13. this() //調用主構造器
  14. this.name = name
  15. }
  16. //另一個輔助構造器
  17. def this(name: String, age: Int) {
  18. this(name) //調用前一個輔助構造器
  19. this.age = age
  20. }
  21. }



主構造器

在Scala中,每個類都有主構造器。主構造器並不以this方法定義,而是與類定義交織在一起。

主構造器的參數直接放在類名之後

代碼示例:

  1. object exam1 {
  2. def main(args: Array[String]): Unit = {
  3. val per: Person = new Person("Bob", 18) //使用主構造器實例化對象
  4. println(per.name + " : " + per.age)
  5. }
  6. }
  7. class Person(val name: String, val age: Int) {
  8. //產生私有的name和age,沒有setter
  9. //.....
  10. }

打印結果:

Bob : 18



上述簡短的Person類定義極大簡化了相同功能的Java代碼:

與上例相同功能的Java代碼示例:

  1. class Person {
  2. private String name;
  3. private int age;
  4. public Person(String name, int age) {
  5. this.name = name;
  6. this.age = age;
  7. }
  8. public String name() {
  9. return this.name;
  10. }
  11. public int age() {
  12. return this.age;
  13. }
  14. }



Scala主構造器定義的所有語句都會執行

代碼示例:

  1. class Person(val name: String, val age: Int) {
  2. //產生私有的name和age,沒有setter
  3. println("主構造器定義的所有語句都會執行") //每次使用主構造器時都會執行
  4. def description = name + "is" + age + "years old"
  5. }



不同類型的主構造器參數對應會生成的字段和方法:

技術分享圖片

如果想讓主構造器變成私有的,可以像這樣放置private關鍵字:

  1. class Person private (val name: String, val age: Int) {
  2. //......
  3. }

這樣一來,類用戶就必須通過輔助構造器來構造Person對象了。

Scala嵌套類

在Scala中,你幾乎可以在任何語法結構中內嵌任何語法構造。你可以在函數中定義函數,在類中定義類。

代碼示例:

  1. class Network {
  2. //在Network類中定義類Member
  3. class Member(val name: String) {
  4. val contacts = new ArrayBuffer[Member]
  5. }
  6. private val members = new ArrayBuffer[Member]
  7. def join(name: String) = {
  8. val m = new Member(name)
  9. members += m
  10. m
  11. }
  12. }

考慮有如下兩個網絡:

  1. val chatter = new Network
  2. val myFace = new Network

在Scala中,每個實例都有它自己的Member類,就和它們有自己的members字段一樣。也就是說chatter.Member和myFace.Member是不同的兩個類。

作用:拿網絡示例來說,你可以在各自的網絡中添加成員,但是不能跨網添加成員。

代碼示例:

  1. val chatter = new Network
  2. val myFace = new Network
  3. val fred = chatter.join("Fred")
  4. val wilma = chatter.join("wilma")
  5. fred.contacts += wilma //OK
  6. val barney = myFace.join("Barney")
  7. //錯誤:不能將一個myFace.Member添加到chatter.Member元素緩沖中
  8. fred.contacts += barney

在嵌套類中,你可以通過 外部類.this 的方式來訪問外部類的this引用,就像Java那樣。

  1. class Network(val name: String) {
  2. //在Network類中定義類Member
  3. class Member(val name: String) {
  4. //....
  5. def description = name + "inside" + Network.this.name
  6. }
  7. }

如果你覺得需要,也可以使用如下語法建立一個指向該引用的別名:

  1. class Network(val name: String) { outer =>
  2. //在Network類中定義類Member
  3. class Member(val name: String) {
  4. //....
  5. def description = name + "inside" + outer.name
  6. }
  7. }

上述語法使得outer變量指向Network.this。對這個變量,你可以使用任何合法的名稱。

Scala類與對象