[Day 12] Class 初始化

之前提到了 class 的 constructor,但如果還想要建立第二或第三個 constructor 呢?

Secondary constructor

在 class 中還可以定義第二個 constructor,這裡使用了 this() 呼叫了原本的 main constructor,這樣我們就可以有一個 constructor 只需傳入一個參數,而其他兩個都是預設值

constructor(id: Long) : this(id, BigDecimal(0), LocalDateTime.now())

在 main 裡面現在要創造一個新的 Wallet 只需要傳入一個參數。

fun main() {
	val wallet2 = Wallet(102)
}

init block (初始化區塊)

在 Kotlin 中還可以定義 多個 初始化區塊,這個區塊目的主要是可以設定變數或值,或對 constructor 傳入的值做一些檢查

下例中,對 paymentWay 初始了一個新的 PaymentWay,還對 balance 做了檢查,如此來可以很結構化的來對一個新的 class 做初始化。

init {
        println("init1")
        paymentWay = PaymentWay("XXX Pay")
        require(balance >= BigDecimal.ZERO) { "錢包金額必須大於或等於 0" }
}

初始的順序

有趣的事,初始化的順序,大家可能都會認為只要是 constructor 應該會優先吧?

但其實執行順序是

  1. 主 constructor
  2. 變數
  3. init block 們
  4. 最後才是第二個 constructor block 內的內容,第三個 constructor block 內的內容...依此類推

來做個測試,當我們利用只有一個參數的 Wallet(102) 創造出了物件

fun main() {
    println("\n\n wallet2 call\n")
    println("run main constructor....")
    val wallet2 = Wallet(102)
}

這裏特別用了 兩個 init block 來測試結果

class Wallet(val id: Long, _balance: BigDecimal, _createTime: LocalDateTime) {

    var balance = _balance
        get() {
            println("取得 balance: $field")
            return field
        }
	......

		init {
		    println("run init1")
		    paymentWay = PaymentWay("XXX Pay")
		    require(balance >= BigDecimal.ZERO) { "錢包金額必須大於或等於 0" }
		}
		
		// 第二個 constructor
		constructor(id: Long) : this(id, BigDecimal(0), LocalDateTime.now()) {
		    println("run secondary constructor")
		}
		
		init {
		    println("run init2")
		}
	......
}

印出的結果是這樣

wallet2 call
run main constructor....
run init1
取得 balance: 0
run init2
run secondary constructor

就會發現執行順序,會像之前描述的是

  • 主 constructor ⇒
  • 有變數了 ⇒
  • init1 (裡面做 balance 檢查,所以印出 取得 balance: 0) ⇒
  • init2 ⇒
  • 最後是 secondary constructor 的內容

這裡就會發現不管 init 定義幾個,都還是會在 secondary constructor 之前。

雖然一開始 val wallet2 = Wallet(102) 是呼叫 secondary constructor ,但其實初始也是呼叫 this() 到 main constructor,在所有變數和 init 跑完後,才會進入 secondary constructor 的 block 內容。

lateinit (延遲初始化)

因為 Kotlin null safety 的安全設計,所以建立 class instance 的時候,都必須完成初始化。

但像 Spring 這樣會使用 IOC 的框架,我們常常使用到注入,把我們所需要的 class 交給 spring 去做 bean 的生命週期管理,也就是什麼時候初始這個 bean 是由 spring 的容器決定的。

lateinit 關鍵字就是為了像這樣的狀況而存在的

這個例子就是在 spring 中宣告了一個 RestControoler,注入了 GreetingService,這裡就加上了 lateinit 來避開 Kotlin 初始化在一開始就要完成的這個要求,允許 GreetingService 可以延後初始化。

@RestController
class GreetingController {
    @Autowired
    lateinit var service: GreetingService
		....
}

以上就是今天的內容,我們明天見~!

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