Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

属性访问器

要读取属性,请使用其名称。要为可变属性分配值,请使用赋值运算符 =

下面的代码读取和写入属性 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
}

属性 capacityfull 没有底层状态-它们在每次访问时被计算。capacityfull 都类似于函数,您也可以将它们定义为函数:

// 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 找到。