天天看點

Kotlin學習筆記(五)

目錄

定義類

計算屬性

防範靜态條件

類的初始化

主構造函數

主構造函數中定義屬性

次構造函數

 預設參數

初始化塊

初始化順序

 延遲初始化

惰性初始化

初始化陷阱

定義類

針對我們定義的每一個屬性,Kotlin都會産生一個field、一個getter和setter,field用來存儲屬性資料,我們不能直接定義field,Kotlin會封裝field,保護它裡面的資料,隻暴露給getter和setter使用。屬性的getter方法決定如何讀取屬性值,每個屬性都有它的getter方法,setter方法決定你如何給屬性指派,是以隻有可變屬性才會有setter方法,盡管Kotlin會自動提供預設的getter和setter,但在需要控制如何讀取屬性資料時,我們也可以自定義它們。
Kotlin學習筆記(五)
import kotlin.math.absoluteValue

fun main() {
    var p = Player()
    p.name = " Honey "
    println(p.name)
    p.age = 18
    println(p.age)
}

class Player {
    var name = "Honey"
        get() = field.capitalize()
        set(value) {
            field = value.trim()
        }
    var age = 17
        get() = field.absoluteValue
        set(value) {
            field = value - 1
        }
}           

計算屬性

計算屬性是 通過一個覆寫的get和set運算符來定義的,這時field就不需要了。
class Player2 {
    var rolledValue = 0
        get() = (1..66).shuffled().first()
}           

防範靜态條件

如果一個類屬性既可空又可變,那麼引用它之前你必須保證它非空,一個辦法是用also标準函數。
class Player3 {
    var words: String? = null
    fun sayWords() {
        words?.also {
            println("Hello,${it.capitalize()}")
        }
    }
}

fun main() {
    val p = Player3()
    p.sayWords()
    p.words = "Kitty"
    p.sayWords()
}           

類的初始化

主構造函數

我們在Player類的定義中定義一個主構造函數,使用臨時變量為Player的各個屬性提供初始值,在Kotlin中,為便于識别,臨時變量(包含僅引用一次的參數),通常都會以下劃線開頭的名字命名。
class Player(_name: String, _age: Int, _score: Double) {
    var name = _name
    var age = _age
    var score = _score
}

fun main() {
    var p = Player("Honey", 17, 100.0)
    println("${p.name}今年${p.age}歲了,本學期成績${p.score}分,好棒鴨!")
}           

主構造函數中定義屬性

class Player(_name: String, var age: Int, var score: Double) {
    var name = _name
}

fun main() {
    var p = Player("Honey", 17, 100.0)
    println("${p.name}今年${p.age}歲了,本學期成績${p.score}分,好棒鴨!")
}           

次構造函數

有主就有次,對應主構造函數的是 次構造函數,我們可以定義多個次構造函數來配置不同的參數組合。
class Player4(_name: String, var age: Int, var score: Double) {
    var name = _name

    constructor(name: String) : this(name, 10, 98.0)
}

fun main() {
    var p2 = Player4("Luffy")
    println("${p2.name}今年${p2.age}歲了,本學期成績${p2.score}分,好棒鴨!")
}           

使用次構造函數也可以定義初始化代碼邏輯:

class Player5(_name: String, _age: Int, _score: Double) {
    var name = _name
    var age = _age
    var score = _score

    constructor(_name: String, _age: Int) : this(_name, _age, 60.0) {
        this.name = _name.toUpperCase()
        this.age = _age - 1
        this.score += 6
    }
}

fun main() {
    var p = Player5("Lucky", 18, 92.0)
    println(p.name)
    println(p.age)
    println(p.score)
}           

 預設參數

定義構造函數時,可以給構造函數參數指定預設值,如果使用者調用時不提供值參,就使用這個預設值。
class Player6(_name: String, var age: Int = 16, var score: Double = 0.0) {
    var name = _name
}

fun main() {
    var p = Player6("Kitty")
    println("${p.name}今年${p.age}歲了,本學期成績${p.score}分。")
}           

初始化塊

初始化塊可以設定變量和值,以及執行有效性檢查,如檢查傳給某構造函數的值是否有效,初始化塊代碼會在構造類執行個體時執行。
class Student(_name: String, _age: Int) {
    var name = _name
    var age = _age

    init {
        require(age > 0) { "Age is illegal" }
        require(name.isNotBlank()) { "Student must have a name." }
    }
}

fun main() {
    var stu = Student("",-1)
}           

初始化順序

  1. 主構造函數裡面聲明的屬性
  2. 類級别的屬性指派
  3. init初始化塊裡的屬性指派和函數調用
  4. 次構造函數裡的屬性指派和函數調用
class Student2(_name: String, var age: Int) {
    var name = _name
        get() = field.capitalize()
        set(value) {
            field = value.trim()
        }
    var hobby: String
    val subject: String

    init {
        println("initializing ...")
        subject = "math"
        hobby = "listen music"
    }

    constructor(_name: String) : this(_name, 17) {
        hobby = "play guitar"
    }
}

fun main() {
    var stu = Student2("Lucky")
}           
Kotlin學習筆記(五)

 延遲初始化

  • 使用lateinit關鍵字相當于做了一個約定:在用它之前負責初始化。
  • 隻要無法确認lateinit變量是否完成初始化,可以執行isInitialized檢查。
class Chef() {
    lateinit var ingredients: String
    fun ready() {
        ingredients = "rice"
    }

    fun cook() {
        if (::ingredients.isInitialized) println(ingredients)
    }
}

fun main() {
    var chef = Chef()
    chef.ready()
    chef.cook()
}           

惰性初始化

class LazyInit(_name: String) {
    var name = _name
    val config by lazy { loadConfig() }
    private fun loadConfig(): String {
        println("loading ...")
        return name.capitalize()
    }
}

fun main() {
    val result = LazyInit("luffy")
    println(result.name)
    Thread.sleep(5000)
    println(result.config)
}           

初始化陷阱

  1. 在使用初始化塊時,順序非常重要,必須保證初始化塊中的所有屬性均已完成初始化。
    Kotlin學習筆記(五)
  2. 下面這段代碼編譯是沒有問題的,因為編譯器看到name屬性已在init中初始化了,但代碼運作會抛出空指針異常,因為name屬性還沒有指派的時候,firstLetter函數就把name拿去用了。
    class InitTrap {
        val name: String
        private fun firstLetter() = name[0]
    
        init {
            println(firstLetter())
            name = "Honey"
        }
    }
    
    fun main() {
        val initTrap = InitTrap()
    }           
  3. 因為編譯器看到所有屬性都初始化了,是以代碼編譯沒問題,但運作結果卻是null,問題出在哪?在用initPlayerName函數初始化playerName時,name屬性還未完成初始化。
    class InitTrap(_name: String) {
        val playerName: String = initName()
        var name = _name
        private fun initName() = name
    
    }
    
    fun main() {
        val initTrap = InitTrap("Honey")
        println(initTrap.playerName)
    }