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 的方法名分別叫做 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 開源專案: 大資料入門指南