Kotlin 语言参考文档 中文版 Help

属性(Property)

在 Kotlin 中, 属性可以用来存储和管理数据, 而不需要编写用于访问或修改数据的函数. 你可以在 类, 接口, 对象, 同伴对象 中使用属性, 甚至可以在这些结构之外, 以顶级属性的形式使用.

每个属性都有一个名称, 一个类型, 以及自动生成的 get() 函数, 称为 getter. 你可以使用 getter 来读取属性的值. 如果属性是可变的, 它还有一个 set() 函数, 称为 setter, 可以修改属性的值.

声明属性

属性可以是可变属性(var), 或只读属性(val). 你可以在 .kt 文件中将它们声明为顶级属性. 顶级属性可以看作一个属于包的全局变量:

// 文件: Constants.kt package my.app val pi = 3.14159 var counter = 0

也可以在类, 接口或对象内声明属性:

// 包含属性的类 class Address { var name: String = "Holmes, Sherlock" var street: String = "Baker" var city: String = "London" } // 包含属性的接口 interface ContactInfo { val email: String } // 包含属性的对象 object Company { var name: String = "Detective Inc." val country: String = "UK" } // 实现接口的类 class PersonContact : ContactInfo { override val email: String = "sherlock@example.com" }

使用属性时, 只需通过属性名来引用它:

class Address { var name: String = "Holmes, Sherlock" var street: String = "Baker" var city: String = "London" } interface ContactInfo { val email: String } object Company { var name: String = "Detective Inc." val country: String = "UK" } class PersonContact : ContactInfo { override val email: String = "sherlock@example.com" } //sampleStart fun copyAddress(address: Address): Address { val result = Address() // 访问 result 实例的属性 result.name = address.name result.street = address.street result.city = address.city return result } fun main() { val sherlockAddress = Address() val copy = copyAddress(sherlockAddress) // 访问 copy 实例的属性 println("Copied address: ${copy.name}, ${copy.street}, ${copy.city}") // 输出结果为: Copied address: Holmes, Sherlock, Baker, London // 访问 Company 对象的属性 println("Company: ${Company.name} in ${Company.country}") // 输出结果为: Company: Detective Inc. in UK val contact = PersonContact() // 访问 contact 实例的属性 println("Email: ${contact.email}") // 输出结果为: Email: sherlock@email.com } //sampleEnd

在 Kotlin 中, 我们推荐在声明属性的同时进行初始化, 以保证代码的安全性和可读性. 但是, 在某些特殊情况下, 可以 延迟初始化.

如果编译器能够从初始化代码或 getter 的返回类型推断出属性的类型, 那么可以省略属性类型的声明:

var initialized = 1 // 推断类型为 Int var allByDefault // 错误: 属性必须初始化.

自定义 getter 与 setter

默认情况下, Kotlin 会自动生成 getter 和 setter. 如果你需要额外的逻辑, 例如校验, 格式化, 或根据其他属性进行计算, 可以定义自定义访问器.

自定义 getter 在每次访问属性时执行:

//sampleStart class Rectangle(val width: Int, val height: Int) { val area: Int get() = this.width * this.height } //sampleEnd fun main() { val rectangle = Rectangle(3, 4) println("Width=${rectangle.width}, height=${rectangle.height}, area=${rectangle.area}") }

如果编译器能够从 getter 推断出属性类型, 则可以省略类型:

val area get() = this.width * this.height

自定义 setter 在每次向属性赋值时执行, 初始化时除外. 按照惯例, setter 的参数名称为 value, 但你也可以选择不同的名称:

class Point(var x: Int, var y: Int) { var coordinates: String get() = "$x,$y" set(value) { val parts = value.split(",") x = parts[0].toInt() y = parts[1].toInt() } } fun main() { val location = Point(1, 2) println(location.coordinates) // 输出结果为: 1,2 location.coordinates = "10,20" println("${location.x}, ${location.y}") // 输出结果为: 10, 20 }

修改可见度或添加注解

在 Kotlin 中, 你可以修改访问器的可见度, 或者添加 注解, 而不需要替换默认实现. 这些修改不必在方法 body 部 {} 内进行.

要修改访问器的可见度, 请在 get 或 set 关键字之前使用可见度修饰符:

class BankAccount(initialBalance: Int) { var balance: Int = initialBalance // 只有类自身能够修改 balance private set fun deposit(amount: Int) { if (amount > 0) balance += amount } fun withdraw(amount: Int) { if (amount > 0 && amount <= balance) balance -= amount } } fun main() { val account = BankAccount(100) println("Initial balance: ${account.balance}") // 输出结果为: 100 account.deposit(50) println("After deposit: ${account.balance}") // 输出结果为: 150 account.withdraw(70) println("After withdrawal: ${account.balance}") // 输出结果为: 80 // account.balance = 1000 // 错误: 无法赋值, 因为 setter 的可见度是 private }

要对访问器添加注解, 请在 get 或 set 关键字之前使用注解:

// 定义一个可应用于 getter 的注解 @Target(AnnotationTarget.PROPERTY_GETTER) annotation class Inject class Service { var dependency: String = "Default Service" // 对 getter 添加注解 @Inject get } fun main() { val service = Service() println(service.dependency) // 输出结果为: Default service println(service::dependency.getter.annotations) // 输出结果为: [@Inject()] println(service::dependency.setter.annotations) // 输出结果为: [] }

这个示例使用 反射 来显示 getter 和 setter 上存在的注解.

后端域变量(Backing Field)

如果属性的值需要存储在内存中, 编译器会自动为属性生成后端域变量(Backing Field).

例如, 当你使用默认的 get() 和 set() 函数时, 编译器会创建后端域变量, 因为它们需要读写存储的值:

var count = 0

在 自定义 get() 或 set() 函数 中, 可以使用 field 关键字来访问后端域变量. 例如, 可以向 getter 或 setter 中添加额外的逻辑, 或者在属性发生变化时触发额外的操作.

在下面的示例中, score 属性在 set() 函数中使用后端域变量, 使得更新值时同时触发一个日志事件:

class Scoreboard { var score: Int = 0 set(value) { field = value // 更新值时添加日志 println("Score updated to $field") } } fun main() { val board = Scoreboard() board.score = 10 // 输出结果为: Score updated to 10 board.score = 20 // 输出结果为: Score updated to 20 }

并不是所有属性都会默认创建后端域变量, 因为有些属性可能不需要. 例如, isEmpty 属性没有后端域变量, 因为每次访问时, 它的值都会从 size 属性计算得到:

val isEmpty: Boolean get() = this.size == 0

明确的后端域变量(Explicit Backing Field)

有时你可能需要更多的灵活性. 例如, 如果你有一个 API, 希望能够在内部修改属性, 但不允许外部修改. 这种情况下, 可以使用 明确的后端域变量(Explicit Backing Field).

在下面的示例中, ShoppingCart 类有一个 items 属性, 代表购物车中的所有商品. 这个类将 items 属性公开为只读的字符串列表, 但在内部通过明确的后端域变量, 将数据存储在一个可变的列表中:

class ShoppingCart { // 使用明确的后端域变量的公开只读视图 val items: List<String> field = mutableListOf() fun addItem(item: String) { items.add(item) } fun removeItem(item: String) { items.remove(item) } } fun main() { val cart = ShoppingCart() cart.addItem("Apple") cart.addItem("Banana") println(cart.items) // 输出结果为: [Apple, Banana] cart.removeItem("Apple") println(cart.items) // 输出结果为: [Banana] }

在这个示例中, 编译器从 mutableListOf() 调用推断后端域变量的类型: MutableList<String>. 你也可以明确的声明后端域变量的类型:

val items: List<String> // 具有明确类型的明确后端域变量 field: MutableList<String> = mutableListOf()

在 ShoppingCart 类的示例中, 编译器将 items 属性智能转换(smart cast)为 MutableList<String> 类型, 因此类可以通过 add() 和 remove() 函数向购物车中添加和删除商品. 在类的外部, 编译器使用公开的属性类型 List<String>, 因此 API 使用者只能读取 items 列表中的内容.

限制

使用明确的后端域变量时, 属性和后端域变量本身必须遵循一定的规则. 属性要使用明确的后端域变量, 必须满足以下条件:

此外, 后端域变量的类型必须是属性类型的子类型, 且必须具有 private 可见度.

要绕过这些限制, 可以改为使用后端属性.

后端属性(Backing Property)

如果明确的后端域变量不适合你的使用场景, 你可以尝试使用一种名为 后端属性(Backing Property) 的编程模式.

例如, 如果你的属性需要自定义 getter:

class UserDirectory { private val _users = mutableListOf( "sarah", "mike", "emma" ) val users: List<String> get() = _users.sorted() fun addUser(username: String) { _users.add(username) } } fun main() { val directory = UserDirectory() directory.addUser("alex") println(directory.users) // 输出结果为: [alex, emma, mike, sarah] }

在这个示例中, UserDirectory 类有一个只读属性 users, 列出目录中的所有用户. _users 变量是 private 的后端属性, 包含真实的列表. public 属性 users 的 getter 先对列表进行排序, 然后返回结果.

编译期常数值

如果只读属性的值在编译期间就能确定, 请使用 const 修饰符, 将它标记为 编译期常数值(Compile-Time Constant). 编译期常数值会在编译时内联(inline), 因此每处引用都会被替换为实际的值. 由于不会调用 getter, 因此访问效率更高:

// 文件: AppConfig.kt package com.example // 编译期常数值 const val MAX_LOGIN_ATTEMPTS = 3

编译期常数值必须满足以下所有条件:

编译期常数值仍然有后端域变量, 因此你可以使用 反射 与它进行交互.

这类属性也可以在注解内使用:

const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated" @Deprecated(SUBSYSTEM_DEPRECATED) fun processLegacyOrders() { ... }

延迟初始化的(Late-Initialized)属性和变量

通常, 属性必须在构造器中进行初始化. 但是, 并不总是方便这样做. 例如, 你可能通过依赖注入来初始化属性, 或者在单元测试的 setup 方法中初始化属性.

要处理这些情况, 请为属性添加 lateinit 修饰符:

public class OrderServiceTest { lateinit var orderService: OrderService @SetUp fun setup() { orderService = OrderService() } @Test fun processesOrderSuccessfully() { // 直接调用 orderService, 无需检查 null, 或初始化状态 orderService.processOrder() } }

你可以对以下声明为 var 的属性使用 lateinit 修饰符:

  • 顶级属性.

  • 局部变量.

  • 类 body 部之内的属性.

对于类属性:

  • 不能在主构造器中声明.

  • 不能有自定义 getter 或 setter.

在所有情况下, 属性或变量的类型必须是非 null 的, 而且不能是 基本类型.

如果在初始化之前访问 lateinit 属性, Kotlin 会抛出一个特定的异常, 指明被访问的属性未初始化:

class ReportGenerator { lateinit var report: String fun printReport() { // 在初始化之前访问, 会抛出异常 println(report) } } fun main() { val generator = ReportGenerator() generator.printReport() // 发生错误: Exception in thread "main" kotlin.UninitializedPropertyAccessException: lateinit property report has not been initialized }

要检查 lateinit var 是否已完成初始化, 请对 属性的引用 使用 isInitialized 属性:

class WeatherStation { lateinit var latestReading: String fun printReading() { // 检查属性是否已初始化 if (this::latestReading.isInitialized) { println("Latest reading: $latestReading") } else { println("No reading available") } } } fun main() { val station = WeatherStation() station.printReading() // 输出结果为: No reading available station.latestReading = "22°C, sunny" station.printReading() // 输出结果为: Latest reading: 22°C, sunny }

只有在代码中已经可以访问某个属性时, 才能对这个属性使用 isInitialized. 该属性必须声明在同一个类中, 在外部类中, 或者是同一文件中的顶级属性.

属性的覆盖

参见 属性的覆盖.

委托属性(Delegated Property)

为了重用逻辑并减少代码重复, 你可以将获取和设置属性的任务委托给另一个单独的对象.

将访问器的行为委托出去, 能使属性的访问器逻辑集中化, 更易于重用. 这种方案在实现以下行为时很有用:

  • 延迟计算属性值.

  • 通过指定的键值从 map 中读取数据.

  • 访问数据库.

  • 在属性被访问时通知监听器.

你可以自己在库中实现这些常见行为, 也可以使用外部库提供的现有委托. 详情请参见 委托属性.

2026/09/29