1. 程式人生 > 程式設計 >Scala 系列(八)—— 類和物件

Scala 系列(八)—— 類和物件

一、初識類和物件

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 開源專案大資料入門指南