ZetCode

Kotlin val 关键字

最后修改于 2025 年 4 月 19 日

Kotlin 的 val 关键字用于声明不可变变量。这些变量只能被赋值一次,并且不能被重新赋值。本教程将通过实际例子深入探讨 val 关键字。

基本定义

Kotlin 中的 val 关键字声明只读变量。一旦初始化,val 就不能被重新赋值。它类似于 Java 中的 final 变量。val 在引用级别上提供不可变性。

基本的 val 声明

val 的最简单用法是声明一个带有初始值的不可变变量。类型可以被显式声明或由 Kotlin 推断。

BasicVal.kt
package com.zetcode

fun main() {

    val name = "John Doe" // Type inferred as String
    val age: Int = 30     // Explicit type declaration
    
    println(name) // Output: John Doe
    println(age)  // Output: 30
}

这里我们声明了两个 val 变量。第一个具有推断类型,而第二个具有显式类型。这两个变量在初始化后都不能被重新赋值。这确保了它们的值保持不变。

val vs var

Kotlin 同时具有 val(不可变)和 var(可变)关键字。val 通过防止意外重新赋值来提供安全性。

ValVsVar.kt
package com.zetcode

fun main() {

    val immutable = "I cannot change"
    var mutable = "I can change"
    
    // immutable = "New value" // Compilation error
    mutable = "New value"     // Allowed
    
    println(immutable) // Output: I cannot change
    println(mutable)   // Output: New value
}

此示例显示了 valvar 之间的关键区别。尝试重新赋值 val 会导致编译错误。当值不应该改变时,始终优先使用 val

val 与自定义类型

val 可用于任何 Kotlin 类型,包括自定义类。引用是不可变的,但对象的属性可能仍然是可变的。

CustomTypeVal.kt
package com.zetcode

class Person(var name: String, var age: Int)

fun main() {

    val person = Person("Alice", 25)
    // person = Person("Bob", 30) // Error: val cannot be reassigned
    
    person.name = "Alice Smith"    // Allowed: object is mutable
    person.age = 26
    
    println("${person.name}, ${person.age}") // Output: Alice Smith, 26
}

这里我们声明了一个 val,它包含一个 Person 对象。虽然我们不能重新赋值 person 变量,但我们可以修改其属性,因为 Person 类使用了 var 属性。这表明 val 仅保护引用。

val 在函数参数中

Kotlin 中的函数参数始终是不可变的,类似于使用 val 声明。你不能在函数体内重新赋值它们。

FunctionParamVal.kt
package com.zetcode

fun printMessage(message: String) {
    // message = "New message" // Error: parameters are vals
    println(message)
}

fun main() {

    val msg = "Hello, Kotlin!"
    printMessage(msg) // Output: Hello, Kotlin!
}

message 参数在函数内部的行为类似于 val。尝试重新赋值它会导致编译错误。这强制了函数参数的不可变性。

val 与延迟初始化

val 可以使用 lateinit(对于 var)或使用自定义 getter 进行延迟初始化。这在延迟初始化时很有用。

LazyVal.kt
package com.zetcode

class Configuration {
    val apiKey: String by lazy {
        // Complex initialization
        System.getenv("API_KEY") ?: "default-key"
    }
}

fun main() {

    val config = Configuration()
    println(config.apiKey) // Initialized on first access
}

在这里,apiKey 是一个 val,它是延迟初始化的。该值仅在首次访问时计算。这种模式对于昂贵的初始化很有用。该值在初始化后保持不变。

val 在伴生对象中

伴生对象中的 val 创建类级别的常量。这些在加载类时被初始化,并在整个执行过程中保持不变。

CompanionVal.kt
package com.zetcode

class MathConstants {
    companion object {
        val PI = 3.14159
        val E = 2.71828
    }
}

fun main() {

    println(MathConstants.PI) // Output: 3.14159
    println(MathConstants.E)  // Output: 2.71828
}

PIE 值是类级别的常量。它们通过类名访问,并且不能被修改。这是数学常量和配置值的常见模式。

val 与集合

当将 val 与集合一起使用时,引用是不可变的,但集合内容可能仍然是可变的。使用不可变集合以实现完全的不可变性。

CollectionVal.kt
package com.zetcode

fun main() {

    val mutableList = mutableListOf(1, 2, 3)
    val immutableList = listOf(1, 2, 3)
    
    mutableList.add(4)       // Allowed
    // immutableList.add(4)  // Error: immutable collection
    
    println(mutableList)    // Output: [1, 2, 3, 4]
    println(immutableList)  // Output: [1, 2, 3]
}

这表明 val 仅使引用不可变。集合的可变性取决于其类型。为了实现完全的不可变性,请使用 listOfsetOfmapOf

val 使用的最佳实践

来源

Kotlin 变量文档

本教程深入介绍了 Kotlin 的 val 关键字,展示了它在不可变变量中的用法。我们探讨了各种场景,包括自定义类型、函数参数和集合。正确使用 val 可以使您的代码更安全、更可预测。

作者

我的名字是 Jan Bodnar,我是一位充满激情的程序员,拥有多年的编程经验。自 2007 年以来,我一直在撰写编程文章。到目前为止,我写了 1400 多篇文章和 8 本电子书。 我拥有超过八年的编程教学经验。

列出 所有 Kotlin 教程