Kotlin教學 | 從零開始學Kotlin | Kotlin入門 | CH09: 屬性(Property)

屬性

以下是 Kotlin 屬性(Property) 的基礎概念:varval 的區別、自訂存取器、延遲初始化(lateinitlazy)、const 常數等。


1. 基本概念

定義屬性

Kotlin 中的屬性可以用以下兩種方式定義:

  1. var:可變屬性(可讀寫)。
  2. val:不可變屬性(唯讀)。

範例:varval

class Person {
    var name: String = "Default Name" // 可讀寫
    val age: Int = 18                // 唯讀
}

fun main() {
    val person = Person()
    println(person.name) // 輸出:Default Name
    person.name = "Alice"
    println(person.name) // 輸出:Alice

    // person.age = 20 // 錯誤:`val` 屬性不可修改
    println(person.age) // 輸出:18
}

2. 預設 gettersetter

Kotlin 中,所有屬性都自動生成預設的 gettersetter

  • val(不可變屬性):僅有 getter
  • var(可變屬性):同時擁有 gettersetter

範例:

class Person {
    var name: String = "Default Name" // 自動生成 getter 和 setter
    val age: Int = 18                // 只有 getter
}

fun main() {
    val person = Person()
    // 使用預設的 getter
    println(person.name) // 輸出:Default Name
    println(person.age)  // 輸出:18

    // 使用預設的 setter
    person.name = "Alice"
    println(person.name) // 輸出:Alice

    // person.age = 25 // 錯誤:`val` 屬性無法修改
}

3. 自訂 gettersetter

Kotlin 允許自訂屬性的 gettersetter,用於在屬性讀取或寫入時執行額外邏輯。

語法

var 屬性名稱: 資料型別 = 初始值
    get() = // 自訂 getter
    set(value) {
        // 自訂 setter
    }

範例:

class Person {
    var name: String = "Default Name"
        get() {
            println("Getter called for name")
            return field // 使用 field 取得屬性值
        }
        set(value) {
            println("Setter called with value: $value")
            field = value // 使用 field 設定屬性值
        }

    var age: Int = 18
        set(value) {
            if (value >= 0) {
                field = value // 僅允許非負數
            } else {
                println("Age cannot be negative")
            }
        }
}

fun main() {
    val person = Person()

    // 測試自訂 getter 和 setter
    println(person.name) // 輸出:Getter called for name -> Default Name
    person.name = "Alice" // 輸出:Setter called with value: Alice
    println(person.name) // 輸出:Getter called for name -> Alice

    person.age = -5 // 輸出:Age cannot be negative
    println(person.age) // 輸出:18
}

關鍵字:field 的作用

  • field 是後備字段(Backing Field)
    • 存儲屬性的實際值。
    • 當屬性有自訂的 gettersetter 時,需用 field 訪問或設置屬性的內部值。

為什麼需要 field

如果在 gettersetter 中直接使用屬性名稱(如 name),會導致遞迴調用自己,最終導致程式崩潰。


錯誤範例:沒有使用 field

class Person {
    var name: String = "Default Name"
        get() {
            return name // 錯誤:導致遞迴調用
        }
        set(value) {
            name = value // 錯誤:導致遞迴調用
        }
}

正確範例:使用 field

class Person {
    var name: String = "Default Name"
        get() = field // 正確使用
        set(value) {
            field = value // 正確使用
        }
}

4. 計算屬性

計算屬性不需要存儲數據,每次訪問時動態計算值。計算屬性僅有 getter,無 setter


範例:

class Rectangle(val width: Int, val height: Int) {
    val area: Int
        get() = width * height // 計算屬性值
}

fun main() {
    val rectangle = Rectangle(5, 10)
    println("Area: ${rectangle.area}") // 輸出:Area: 50
}

5. 延遲初始化屬性

Kotlin 提供兩種方式延遲初始化屬性:lateinitlazy


5.1 lateinit

  • 用於延遲初始化可變屬性(var)。
  • 必須檢查是否已初始化,否則會拋出異常。
  • 常用於類別初始化時無法確定值的情況。

範例:

class Person {
    lateinit var name: String

    fun initializeName(value: String) {
        name = value
    }

    fun printName() {
        if (::name.isInitialized) {
            println("Name: $name")
        } else {
            println("Name is not initialized")
        }
    }
}

fun main() {
    val person = Person()
    person.printName() // 輸出:Name is not initialized
    person.initializeName("Alice")
    person.printName() // 輸出:Name: Alice
}

5.2 lazy

  • 用於延遲初始化唯讀屬性(val)。
  • 第一次訪問時執行初始化邏輯,且僅執行一次。

範例:

class Person {
    val greeting: String by lazy {
        println("Initializing greeting...")
        "Hello, Kotlin!"
    }
}

fun main() {
    val person = Person()
    println("Before accessing greeting")
    println(person.greeting) // 第一次訪問時初始化,輸出:Initializing greeting... -> Hello, Kotlin!
    println(person.greeting) // 輸出:Hello, Kotlin!
}

6. 常數(const

const 修飾的屬性

  • 必須在 objectcompanion object 中定義。
  • 必須是基本型別(StringInt 等)。
  • 編譯時期常數,無法修改。

範例:

class Constants {
    companion object {
        const val MAX_USERS = 100
    }
}

fun main() {
    println("Max users: ${Constants.MAX_USERS}") // 輸出:Max users: 100
}

7. 屬性的可見性修飾符

Kotlin 提供以下可見性修飾符,用於控制屬性的存取範圍:

  1. public(預設):所有地方可見。
  2. private:僅在類別內部可見。
  3. protected:在類別及其子類別中可見。
  4. internal:在同一模組內可見。

範例:可見性修飾符

open class Animal {
    private var privateProp = "Private"
    protected var protectedProp = "Protected"
    internal var internalProp = "Internal"
    public var publicProp = "Public"

    fun printProps() {
        println("$privateProp, $protectedProp, $internalProp, $publicProp")
    }
}

class Dog : Animal() {
    fun printDogProps() {
        // println(privateProp) // 錯誤:private 無法存取
        println(protectedProp) // 可存取
        println(internalProp)  // 可存取
        println(publicProp)    // 可存取
    }
}

fun main() {
    val dog = Dog()
    dog.printDogProps()
    // dog.privateProp // 錯誤:無法存取 private 屬性
    // dog.protectedProp // 錯誤:無法存取 protected 屬性
    println(dog.publicProp) // 輸出:Public
}

8. 擴展屬性

Kotlin 支援為現有類型定義擴展屬性。


範例:

val String.wordCount: Int
    get() = this.split(" ").size

fun main() {
    val text = "Kotlin is awesome"
    println("Word count: ${text.wordCount}") // 輸出:Word count: 3
}

Reference

https://kotlinlang.org/docs/properties.html

發佈留言