今天要開始進入 Kotlin OOP 的部分,class 類別的部分
這裡用一個 Wallet 錢包 class,來解釋 class 的用法
首先建立一個 class,Wallet
最簡單宣告一個 class 的方式,連 大括號 {}
都不需要喔!
class Wallet
建立一個新的 wallet 的 instance,以前在 Java 我可能會說 new 一個 instance,但 Kotlin 不需要寫 new 這個關鍵字
fun main() {
val wallet = Wallet()
}
在 Kotlin 的 class,constructor 宣告的方式跟 Java 不大一樣
宣告的方式如下
class Wallet()
在 class name 後面加上括號,這樣就是一個無參數的 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 裡面任何變數耶...
回到 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 這些套件, 才能幫我們自動產生, 這真的是一個很好的設計
其實在 Kotlin 中,class 裡面定義的變數,都直接稱為屬性( property )
屬性( property) = field + accessor
這裏來解釋一下 property 和 field 的差別
在 Java 裡面
field 是 class 裡面的變數
public class Sample{
int data = 90;
static data = 145;
}
這樣是屬性
屬性(property) = field + accessor
public class Sample{
private int name;
public String getName(){
return this.number;
}
public void setName(String name){
this.name = name;
}
}
而 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() 呢?
把 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")
}
}
但如果只是這樣寫,會發現有個錯
上面的錯誤提示我們應該使用 field 來取的 balance 這個變數的值,什麼是 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 的話題! 謝謝大家
今日練習的程式在這: 請點我