Scala 系列(八)—— 類和對象
- 2019 年 10 月 3 日
- 筆記
一、初識類和對象
Scala 的類與 Java 的類具有非常多的相似性,示例如下:
// 1. 在 scala 中,類不需要用 public 聲明,所有的類都具有公共的可見性 class Person { // 2. 聲明私有變量,用 var 修飾的變量默認擁有 getter/setter 屬性 private var age = 0 // 3.如果聲明的變量不需要進行初始賦值,此時 Scala 就無法進行類型推斷,所以需要顯式指明類型 private var name: String = _ // 4. 定義方法,應指明傳參類型。返回值類型不是必須的,Scala 可以自動推斷出來,但是為了方便調用者,建議指明 def growUp(step: Int): Unit = { age += step } // 5.對於改值器方法 (即改變對象狀態的方法),即使不需要傳入參數,也建議在聲明中包含 () def growUpFix(): Unit = { age += 10 } // 6.對於取值器方法 (即不會改變對象狀態的方法),不必在聲明中包含 () def currentAge: Int = { age } /** * 7.不建議使用 return 關鍵字,默認方法中最後一行代碼的計算結果為返回值 * 如果方法很簡短,甚至可以寫在同一行中 */ def getName: String = name } // 伴生對象 object Person { def main(args: Array[String]): Unit = { // 8.創建類的實例 val counter = new Person() // 9.用 var 修飾的變量默認擁有 getter/setter 屬性,可以直接對其進行賦值 counter.age = 12 counter.growUp(8) counter.growUpFix() // 10.用 var 修飾的變量默認擁有 getter/setter 屬性,可以直接對其進行取值,輸出: 30 println(counter.age) // 輸出: 30 println(counter.currentAge) // 輸出: null println(counter.getName) } }
二、類
2.1 成員變量可見性
Scala 中成員變量的可見性默認都是 public,如果想要保證其不被外部干擾,可以聲明為 private,並通過 getter 和 setter 方法進行訪問。
2.2 getter和setter屬性
getter 和 setter 屬性與聲明變量時使用的關鍵字有關:
- 使用 var 關鍵字:變量同時擁有 getter 和 setter 屬性;
- 使用 val 關鍵字:變量只擁有 getter 屬性;
- 使用 private[this]:變量既沒有 getter 屬性、也沒有 setter 屬性,只能通過內部的方法訪問;
需要特別說明的是:假設變量名為 age,則其對應的 get 和 set 的方法名分別叫做 age
和 age_=
。
class Person { private val name = "heibaiying" private var age = 12 private[this] var birthday = "2019-08-08" // birthday 只能被內部方法所訪問 def getBirthday: String = birthday } object Person { def main(args: Array[String]): Unit = { val person = new Person person.age = 30 println(person.name) println(person.age) println(person.getBirthday) } }
解釋說明:
示例代碼中
person.age=30
在執行時內部實際是調用了方法person.age_=(30)
,而person.age
內部執行時實際是調用了person.age()
方法。想要證明這一點,可以對代碼進行反編譯。同時為了說明成員變量可見性的問題,我們對下面這段代碼進行反編譯:class Person { var name = "" private var age = "" }
依次執行下面編譯命令:
> scalac Person.scala > javap -private Person
編譯結果如下,從編譯結果可以看到實際的 get 和 set 的方法名 (因為 JVM 不允許在方法名中出現=,所以它被翻譯成$eq),同時也驗證了成員變量默認的可見性為 public。
Compiled from "Person.scala" public class Person { private java.lang.String name; private java.lang.String age; public java.lang.String name(); public void name_$eq(java.lang.String); private java.lang.String age(); private void age_$eq(java.lang.String); public Person(); }
2.3 @BeanProperty
在上面的例子中可以看到我們是使用 .
來對成員變量進行訪問的,如果想要額外生成和 Java 中一樣的 getXXX 和 setXXX 方法,則需要使用@BeanProperty 進行註解。
class Person { @BeanProperty var name = "" } object Person { def main(args: Array[String]): Unit = { val person = new Person person.setName("heibaiying") println(person.getName) } }
2.4 主構造器
和 Java 不同的是,Scala 類的主構造器直接寫在類名後面,但注意以下兩點:
- 主構造器傳入的參數默認就是 val 類型的,即不可變,你沒有辦法在內部改變傳參;
- 寫在主構造器中的代碼塊會在類初始化的時候被執行,功能類似於 Java 的靜態代碼塊
static{}
class Person(val name: String, val age: Int) { println("功能類似於 Java 的靜態代碼塊 static{}") def getDetail: String = { //name="heibai" 無法通過編譯 name + ":" + age } } object Person { def main(args: Array[String]): Unit = { val person = new Person("heibaiying", 20) println(person.getDetail) } } 輸出: 功能類似於 Java 的靜態代碼塊 static{} heibaiying:20
2.5 輔助構造器
輔助構造器有兩點硬性要求:
- 輔助構造器的名稱必須為 this;
- 每個輔助構造器必須以主構造器或其他的輔助構造器的調用開始。
class Person(val name: String, val age: Int) { private var birthday = "" // 1.輔助構造器的名稱必須為 this def this(name: String, age: Int, birthday: String) { // 2.每個輔助構造器必須以主構造器或其他的輔助構造器的調用開始 this(name, age) this.birthday = birthday } // 3.重寫 toString 方法 override def toString: String = name + ":" + age + ":" + birthday } object Person { def main(args: Array[String]): Unit = { println(new Person("heibaiying", 20, "2019-02-21")) } }
2.6 方法傳參不可變
在 Scala 中,方法傳參默認是 val 類型,即不可變,這意味着你在方法體內部不能改變傳入的參數。這和 Scala 的設計理念有關,Scala 遵循函數式編程理念,強調方法不應該有副作用。
class Person() { def low(word: String): String = { word="word" // 編譯無法通過 word.toLowerCase } }
三、對象
Scala 中的 object(對象) 主要有以下幾個作用:
- 因為 object 中的變量和方法都是靜態的,所以可以用於存放工具類;
- 可以作為單例對象的容器;
- 可以作為類的伴生對象;
- 可以拓展類或特質;
- 可以拓展 Enumeration 來實現枚舉。
3.1 工具類&單例&全局靜態常量&拓展特質
這裡我們創建一個對象 Utils
,代碼如下:
object Utils { /* *1. 相當於 Java 中的靜態代碼塊 static,會在對象初始化時候被執行 * 這種方式實現的單例模式是餓漢式單例,即無論你的單例對象是否被用到, * 都在一開始被初始化完成 */ val person = new Person // 2. 全局固定常量 等價於 Java 的 public static final val CONSTANT = "固定常量" // 3. 全局靜態方法 def low(word: String): String = { word.toLowerCase } }
其中 Person 類代碼如下:
class Person() { println("Person 默認構造器被調用") }
新建測試類:
// 1.ScalaApp 對象擴展自 trait App object ScalaApp extends App { // 2.驗證單例 println(Utils.person == Utils.person) // 3.獲取全局常量 println(Utils.CONSTANT) // 4.調用工具類 println(Utils.low("ABCDEFG")) } // 輸出如下: Person 默認構造器被調用 true 固定常量 abcdefg
3.2 伴生對象
在 Java 中,你通常會用到既有實例方法又有靜態方法的類,在 Scala 中,可以通過類和與類同名的伴生對象來實現。類和伴生對象必須存在與同一個文件中。
class Person() { private val name = "HEIBAIYING" def getName: String = { // 調用伴生對象的方法和屬性 Person.toLow(Person.PREFIX + name) } } // 伴生對象 object Person { val PREFIX = "prefix-" def toLow(word: String): String = { word.toLowerCase } def main(args: Array[String]): Unit = { val person = new Person // 輸出 prefix-heibaiying println(person.getName) } }
3.3 實現枚舉類
Scala 中沒有直接提供枚舉類,需要通過擴展 Enumeration
,並調用其中的 Value 方法對所有枚舉值進行初始化來實現。
object Color extends Enumeration { // 1.類型別名,建議聲明,在 import 時有用 type Color = Value // 2.調用 Value 方法 val GREEN = Value // 3.只傳入 id val RED = Value(3) // 4.只傳入值 val BULE = Value("blue") // 5.傳入 id 和值 val YELLOW = Value(5, "yellow") // 6. 不傳入 id 時,id 為上一個聲明變量的 id+1,值默認和變量名相同 val PINK = Value }
使用枚舉類:
// 1.使用類型別名導入枚舉類 import com.heibaiying.Color.Color object ScalaApp extends App { // 2.使用枚舉類型,這種情況下需要導入枚舉類 def printColor(color: Color): Unit = { println(color.toString) } // 3.判斷傳入值和枚舉值是否相等 println(Color.YELLOW.toString == "yellow") // 4.遍歷枚舉類和值 for (c <- Color.values) println(c.id + ":" + c.toString) } //輸出 true 0:GREEN 3:RED 4:blue 5:yellow 6:PINK
參考資料
- Martin Odersky . Scala 編程 (第 3 版)[M] . 電子工業出版社 . 2018-1-1
- 凱.S.霍斯特曼 . 快學 Scala(第 2 版)[M] . 電子工業出版社 . 2017-7
更多大數據系列文章可以參見 GitHub 開源項目: 大數據入門指南