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 的方法名分別叫做 ageage_=

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

參考資料

  1. Martin Odersky . Scala 編程 (第 3 版)[M] . 電子工業出版社 . 2018-1-1
  2. 凱.S.霍斯特曼 . 快學 Scala(第 2 版)[M] . 電子工業出版社 . 2017-7

更多大數據系列文章可以參見 GitHub 開源項目大數據入門指南