在 Kotlin 中,成员变量(属性)是类的核心组成部分。Kotlin 提供了 var、val、lateinit、by lazy 等关键字,以及自定义 getter 和 setter,帮助开发者更灵活地管理对象的状态。这篇文章将深入介绍这些概念,并给出适用场景和最佳实践。

1. var 和 val

var(可变变量)

使用 var 关键字声明的变量是可变的,可以在后续代码中修改其值。

class Person {
    var name: String = "Unknown"
}

fun main() {
    val person = Person()
    person.name = "Alice"  // ✅ 允许修改
    println(person.name)  // 输出:Alice
}

val(不可变变量)

使用 val 关键字声明的变量是只读的,它的值在初始化后就不能被修改。

class Car {
    val brand: String = "Toyota"
}

fun main() {
    val car = Car()
    // car.brand = "Honda"  // ❌ 编译错误:val 不能被重新赋值
    println(car.brand)  // 输出:Toyota
}

📌 注意:val 变量的引用不可变,但如果它指向的是一个可变对象(如 List),对象本身的内容是可以修改的。

class DataHolder {
    val numbers = mutableListOf(1, 2, 3)
}

fun main() {
    val holder = DataHolder()
    holder.numbers.add(4)  // ✅ 允许修改列表内容
    println(holder.numbers)  // 输出:[1, 2, 3, 4]
}

2. lateinit(延迟初始化)

lateinit var

lateinit 关键字用于修饰 var 变量,表示该变量会稍后初始化,但不能用于 val。

class User {
    lateinit var username: String

    fun initialize() {
        username = "JohnDoe"
    }
}

fun main() {
    val user = User()
    user.initialize()
    println(user.username)  // 输出:JohnDoe
}

📌 注意:

lateinit 变量不能用于基本数据类型(如 Int、Double)。

如果在初始化前访问 lateinit 变量,会抛出 UninitializedPropertyAccessException。

fun main() {
    val user = User()
    println(user.username)  // ❌ 抛出异常:UninitializedPropertyAccessException
}

3. by lazy(懒加载)

by lazy 适用于 只读(val)变量,它会在第一次访问时初始化,并且结果会被缓存。

class Config {
    val settings: String by lazy {
        println("Loading settings...")
        "Dark Mode Enabled"
    }
}

fun main() {
    val config = Config()
    println("Before accessing settings")
    println(config.settings)  // 第一次访问时会执行初始化
    println(config.settings)  // 之后访问直接返回缓存值
}

输出:

Before accessing settings
Loading settings...
Dark Mode Enabled
Dark Mode Enabled

📌 适用场景:

计算成本较高的变量(如读取文件、数据库查询)。

仅在需要时才进行初始化,避免不必要的资源消耗。

4. 自定义 getter 和 setter

自定义 getter

如果希望在每次访问属性时动态计算其值,可以使用 get() 方法:

class Rectangle(val width: Int, val height: Int) {
    val area: Int
        get() = width * height  // 计算面积
}

fun main() {
    val rect = Rectangle(5, 10)
    println(rect.area)  // 输出:50
}

自定义 setter

set() 允许在属性赋值时执行额外的逻辑,例如数据校验或触发某些操作:

class User {
    var age: Int = 0
        set(value) {
            if (value < 0) {
                throw IllegalArgumentException("Age cannot be negative")
            }
            field = value  // `field` 代表实际存储的值
        }
}

fun main() {
    val user = User()
    user.age = 25  // ✅ 正常赋值
    println(user.age)

    user.age = -5  // ❌ 抛出异常
}

📌 注意:

field 关键字用于访问当前属性的真实存储值,避免无限递归。

在 Java 字节码中,Kotlin 的 field 对应的是 Java 类中的私有成员变量。

使用 javap -c Example.class 反编译 Java 字节码后,我们可以看到类似的字节码指令(简化版):

private int value = 0;

public int getValue() {
    return this.value + 1;// 如果没有 `field` 关键字,则是return getValue() + 1;
}

而如果没有 field 关键字,Kotlin 代码会不断调用自身 getValue() 方法,从而导致无限递归。

5. const val(编译期常量)

const val 用于声明编译期常量,只能修饰顶层变量或 object 内部的变量,并且类型只能是基本数据类型或 String。

const val API_URL = "https://example.com/api"

fun main() {
    println(API_URL)
}

❌ 错误示例:

class Config {
    const val TIMEOUT = 5000  // ❌ 编译错误:const 不能用于类的成员变量
}

✅ 正确做法(放在 companion object 内):

class Config {
    companion object {
        const val TIMEOUT = 5000
    }
}

6. companion object(伴生对象)

Kotlin 类没有 static 关键字,但可以使用 companion object 定义伴生对象,类似 Java 的 static 变量和方法。

class Database {
    companion object {
        val CONNECTION_TIMEOUT = 5000
    }
}

fun main() {
    println(Database.CONNECTION_TIMEOUT)  // 直接访问
}

结论

Kotlin 提供了多种管理成员变量的方式,每种方式都有其适用场景:

关键字

适用场景

var

声明可变变量(值可修改)

val

声明只读变量(值不可修改,类似 Java 的 final)

lateinit var

延迟初始化变量(用于非空类型且不能用于基本数据类型,如 Int、Boolean)

by lazy

惰性初始化(仅在首次访问时计算值,并缓存结果,常用于 val 属性)

const val

声明编译期常量(仅限顶层或 companion object 中,且类型需为基本类型或 String)

getter/setter

自定义属性的访问或修改逻辑(如数据验证、计算属性等)

合理使用这些特性,可以让你的 Kotlin 代码更加简洁、高效! 

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐