属性访问器
要读取属性,请使用其名称。要为可变属性分配值,请使用赋值运算符
=
。
下面的代码读取和写入属性 i
:
// PropertyAccessors/Data.kt
package propertyaccessors
import atomictest.eq
class Data(var i: Int)
fun main() {
val data = Data(10)
data.i eq 10 // 读取 'i' 属性
data.i = 20 // 写入 'i' 属性
}
这似乎是直接访问名为i
的存储位置。然而,Kotlin 在执行读取和写入操作时调用函数。默认情况下,这些函数的行为是读取和写入存储在i
中的数据。在本节中,您将学习如何编写自己的属性访问器,以自定义读取和写入操作。
用于获取属性值的访问器称为getter。您可以在属性定义后立即定义 get()
来创建一个getter。用于修改可变属性的访问器称为setter。您可以在属性定义后立即定义 set()
来创建一个setter。
以下示例中定义的属性访问器模仿了Kotlin生成的默认实现。我们显示额外的信息,以便您可以看到在读取和写入过程中实际调用了属性访问器。我们将 get()
和 set()
进行了缩进,以使其与属性视觉上关联起来,但实际关联是因为 get()
和 set()
是紧跟在属性后面定义的(Kotlin 不关心缩进):
// PropertyAccessors/Default.kt
package propertyaccessors
import atomictest.*
class Default {
var i: Int = 0
get() {
trace("get()")
return field // [1]
}
set(value) {
trace("set($value)")
field = value // [2]
}
}
fun main() {
val d = Default()
d.i = 2
trace(d.i)
trace eq """
set(2)
get()
2
"""
}
get()
和 set()
的定义顺序是无关紧要的。您可以定义 get()
而不定义 set()
,反之亦然。
属性的默认行为是从getter返回其存储值,并通过setter修改它——即 [1] 和 [2] 的操作。在getter和setter内部,通过使用 field
关键字来间接操作存储的值,field
只能在这两个函数内部访问。
下面的示例使用getter的默认实现,并添加一个setter来跟踪属性 n
的更改:
// PropertyAccessors/LogChanges.kt
package propertyaccessors
import atomictest.*
class LogChanges {
var n: Int = 0
set(value) {
trace("$field becomes $value")
field = value
}
}
fun main() {
val lc = LogChanges()
lc.n eq 0
lc.n = 2
lc.n eq 2
trace eq "0 becomes 2"
}
如果将属性定义为 private
,那么访问器都将变为 private
。您还可以将setter设置为 private
,getter设置为 public
。这样,您就可以在类外部读取属性,但只能在类内部更改其值:
// PropertyAccessors/Counter.kt
package propertyaccessors
import atomictest.eq
class Counter {
var value: Int = 0
private set
fun inc() = value++
}
fun main() {
val counter = Counter()
repeat(10) {
counter.inc()
}
counter.value eq 10
}
使用 private set
,我们控制了 value
属性,使其只能递增。
普通属性将其数据存储在一个字段中。您还可以创建一个没有字段的属性:
// PropertyAccessors/Hamsters.kt
package propertyaccessors
import atomictest.eq
class Hamster(val name: String)
class Cage(private val maxCapacity: Int) {
private val hamsters =
mutableListOf<Hamster>()
val capacity: Int
get() = maxCapacity - hamsters.size
val full: Boolean
get() = hamsters.size == maxCapacity
fun put(hamster: Hamster): Boolean =
if (full)
false
else {
hamsters += hamster
true
}
fun take(): Hamster =
hamsters.removeAt(0)
}
fun main() {
val cage = Cage(2)
cage.full eq false
cage.capacity eq 2
cage.put(Hamster("Alice")) eq true
cage.put(Hamster("Bob")) eq true
cage.full eq true
cage.capacity eq 0
cage.put(Hamster("Charlie")) eq false
cage.take()
cage.capacity eq 1
}
属性 capacity
和 full
没有底层状态-它们在每次访问时被计算。capacity
和 full
都类似于函数,您也可以将它们定义为函数:
// PropertyAccessors/Hamsters2.kt
package propertyaccessors
class Cage2(private val maxCapacity: Int) {
private val hamsters =
mutableListOf<Hamster>()
fun capacity(): Int =
maxCapacity - hamsters.size
fun isFull(): Boolean =
hamsters.size == maxCapacity
}
在这种情况下,使用属性可以提高可读性,因为容量和满度是笼子的属性。然而,不要仅仅将所有的函数都转换为属性-首先查看它们的可读性。
- -
Kotlin 风格指南更喜欢在对象状态未更改的情况下,当值计算成本低且每次调用返回相同结果时,使用属性而不是函数。
属性访问器为属性提供了一种保护方式。许多面向对象的语言依靠将物理字段设置为 private
来控制对该属性的访问。使用属性访问器,您可以添加代码来控制或修改对该属性的访问,同时允许任何人使用该属性。
练习和解答可以在 www.AtomicKotlin.com 找到。