[Day 10] Class (類別) vol.1

今天要開始進入 Kotlin OOP 的部分,class 類別的部分

宣告一個 class

這裡用一個 Wallet 錢包 class,來解釋 class 的用法

首先建立一個 class,Wallet

https://ithelp.ithome.com.tw/upload/images/20200919/20129902E0nfncr142.png

最簡單宣告一個 class 的方式,連 大括號 {} 都不需要喔!

class Wallet

不需要 new 關鍵字

建立一個新的 wallet 的 instance,以前在 Java 我可能會說 new 一個 instance,但 Kotlin 不需要寫 new 這個關鍵字

fun main() {
    val wallet = Wallet()
}

constructor

無參數的 constructor

在 Kotlin 的 class,constructor 宣告的方式跟 Java 不大一樣

宣告的方式如下

class Wallet()

在 class name 後面加上括號,這樣就是一個無參數的 constructor

有參數的 constructor

注意到這裡的變數名稱都有個底線,這是 Kotlin 為了辨識臨時變數,只參照一次的參數,通常都會以底線 _ 開頭

這裡定義 Wallet constructor 有三個參數

_id: 錢包 id

_balance: 金額

_createTime: 創造的時間

class Wallet(_id: Long, _balance: BigDecimal, _createTime: LocalDateTime)

建立時當然也要改成傳入三個參數

fun main() {
		val wallet = Wallet(9992233L, BigDecimal(200), LocalDateTime.now())
}

這時候會發現,好像無法存取 class 裡面任何變數耶...

https://ithelp.ithome.com.tw/upload/images/20200919/20129902I8dwyJk06A.png

回到 Wallet class,加上三個變數,並把 constructor 帶來的值賦予給這三個變數

class Wallet(_id: Long, _balance: BigDecimal, _createTime: LocalDateTime) {
    val id = _id
    val balance = _balance
    val createTime = _createTime
}

可以順利存取了

fun main() {
    val wallet = Wallet(9992233L, BigDecimal(200), LocalDateTime.now())

    println(wallet.balance)
    println(wallet.id)
}

但這時候, 會發現到一件事,class 裡的變數沒有宣告 private 寫 getter 和 setter,以往在 Java 裡面, 要求的封裝呢?

打開 Kotlin show bytecodes,可以發現到,其實 Kotlin 幫我們都做好了! 真是佛心啊

以往在 spring 開發都還要特別用 lombok 這些套件, 才能幫我們自動產生, 這真的是一個很好的設計

https://ithelp.ithome.com.tw/upload/images/20200919/20129902CORjOI72kD.png

其實在 Kotlin 中,class 裡面定義的變數,都直接稱為屬性( property )

屬性( property) = field + accessor

property vs field

這裏來解釋一下 property 和 field 的差別

field

在 Java 裡面 field 是 class 裡面的變數

public class Sample{
   int data = 90;
   static data = 145;
}

property

這樣是屬性 屬性(property) = field + accessor

public class Sample{
   private int name;
   public String getName(){
      return this.number;
   }
   public void setName(String name){
      this.name = name;
   }
}

Kotlin class 的中的變數 - property

而 Kotlin 強調的簡潔的語法,而不是要寫一堆像 getter setter 這類重複的程式碼

所以當我們宣告一個 class 的變數的時候,也就是宣告 property,其中就默默包含了 getter() setter()

當我們使用 wallet.id 的時候,其實也就是在存取 wallet 的屬性,等同於呼叫了 wallet.getId()

這樣的作法其實也跟 JS 存取物件的方式一樣(如果不是寫 class 的方式的話),個人覺得更簡單,直覺

剛剛寫的這段,其實還可以寫得更簡單

class Wallet(_id: Long, _balance: BigDecimal, _createTime: LocalDateTime) {
    val id = _id
    val balance = _balance
    val createTime = _createTime
}

可以寫成這樣,直接在 constructor,把傳入值塞到屬性變數上

class Wallet(val id: Long, val balance: BigDecimal, val createTime: LocalDateTime) {

}

目前宣告的這三個變數,都是 val,代表只有 getter(),但 balance 應該是要可以被修改的,所以先把 balance 改成 var 宣告

class Wallet(val id: Long, var balance: BigDecimal, val createTime: LocalDateTime) {
}

如此就可以針對 balance 修改啦!

wallet.balance = BigDecimal(300)

客製化自己的 getter(), setter()

那如果想要自己的 getter() 和 setter() 呢?

把 balance 獨立出來成一個 var 變數,之後再下面再來寫 get() 和 set()

這裡我還多定義了一個 balaceUpdateTime 和他的 set 方法

class Wallet(val id: Long, _balance: BigDecimal, _createTime: LocalDateTime) {
    var balance = _balance
        get() {
            println("取得 balance")
//            println("get balance $field")
////            return field
        }
        set(value) {
            println("更新 balance $value")
        }

    var balaceUpdateTime: LocalDateTime? = null
        set(value) {
            println("更新 balance 時間 $value")
        }
}

但如果只是這樣寫,會發現有個錯

https://ithelp.ithome.com.tw/upload/images/20200919/20129902UzsnVwdKK2.png

上面的錯誤提示我們應該使用 field 來取的 balance 這個變數的值,什麼是 field?

backing field (支援欄位)

backing field (支援欄位) 其實是存放了 balance 這個變數的值,為什麼不能直接用 balance 呢?

像這樣

var balance = _balance
    get() {
        return balance 
    }

因為這樣轉成 Java 會變成這樣

public BigDecimal getBalance() {
		return getBalance();
}

會造成遞迴的呼叫啊!

所以在自定義的 get(), set() 中都要使用 field 來表示目前的變數

也就是這樣,這裡補上了 set(value) 的行為

class Wallet(val id: Long, _balance: BigDecimal, _createTime: LocalDateTime) {
    var balance = _balance
        get() {
            println("取得 balance: $field")
            return field
        }
        set(value) {
            println("更新 balance: $value")
            field = value
        }

    var balaceUpdateTime: LocalDateTime? = null
        set(value) {
            println("更新 balance 時間: $value")
            field = value
        }
}

如此在 main 對 balance 和 balaceUpdateTime 更新的時候

fun main() {
    val wallet = Wallet(101, BigDecimal(200), LocalDateTime.now())

    wallet.balance = BigDecimal(200)
    wallet.balaceUpdateTime = LocalDateTime.now()
}

就會印出下面的結果

更新 balance: 200
更新 balance 時間: 2020-09-19T14:51:36.242

今天就先講到這吧! 明天在繼續 class 的話題! 謝謝大家

今日練習的程式在這: 請點我