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

属性委托工具

标准库包含特殊的属性委托操作。

Map 是 Kotlin 标准库中少数几种预配置用作委托属性的类型之一。一个 Map 可以用于存储类中的所有属性。每个属性标识符都成为映射的 String 键,属性的类型则在关联的值中捕获:

// DelegationTools/CarService.kt
package propertydelegation
import atomictest.eq

class Driver(
  map: MutableMap<String, Any?>
) {
  var name: String by map
  var age: Int by map
  var id: String by map
  var available: Boolean by map
  var coord: Pair<Double, Double> by map
}

fun main() {
  val info = mutableMapOf<String, Any?>(
    "name" to "Bruno Fiat",
    "age" to 22,
    "id" to "X97C111",
    "available" to false,
    "coord" to Pair(111.93, 1231.12)
  )
  val driver = Driver(info)
  driver.available eq false
  driver.available = true
  info eq "{name=Bruno Fiat, age=22, " +
    "id=X97C111, available=true, " +
    "coord=(111.93, 1231.12)}"
}

注意,当设置 driver.available = true 时,原始的 Map info 被修改。这是因为 Kotlin 标准库包含了 Map 扩展函数 getValue()setValue(),它们使得属性委托成为可能。以下是它们的简化版本,展示了它们的工作原理:

// DelegationTools/MapAccessors.kt
package delegationtools
import kotlin.reflect.KProperty

operator fun MutableMap<String, Any>.getValue(
  thisRef: Any?, property: KProperty<*>
): Any? {
  return this[property.name]
}

operator fun MutableMap<String, Any>.setValue(
  thisRef: Any?, property: KProperty<*>,
  value: Any
) {
  this[property.name] = value
}

要查看实际的库定义,在 IntelliJ IDEA 或 Android Studio 中将光标放在 by 关键字上,然后调用 “转到声明”

Delegates.observable() 可以观察可变属性的修改。在这里,我们追踪旧值和新值:

// DelegationTools/Team.kt
package delegationtools
import kotlin.properties.Delegates.observable
import atomictest.eq

class Team {
  var msg = ""
  var captain: String by observable("<0>") {
    prop, old, new ->
      msg += "${prop.name} $old to $new "
  }
}

fun main() {
  val team = Team()
  team.captain = "Adam"
  team.captain = "Amanda"
  team.msg eq "captain <0> to Adam " +
    "captain Adam to Amanda"
}

observable() 接受两个参数:

  1. 属性的初始值;在这里是 "<0>"
  2. 一个函数,当属性被修改时要执行的操作。在这里,我们使用了一个 lambda。函数的参数是正在更改的属性、该属性当前的值以及正在更改为的值。

Delegates.vetoable() 允许您在新属性值不满足给定谓词时阻止对属性的更改。在这里,aName() 坚持要求团队队长的名字以字母 “A” 开头:

// DelegationTools/TeamWithTraditions.kt
package delegationtools
import atomictest.*
import kotlin.properties.Delegates
import kotlin.reflect.KProperty

fun aName(
  property: KProperty<*>,
  old: String,
  new: String
) = if (new.startsWith("A")) {
  trace("$old -> $new")
  true
} else {
  trace("Name must start with 'A'")
  false
}

interface Captain {
  var captain: String
}

class TeamWithTraditions : Captain {
  override var captain: String
    by Delegates.vetoable("Adam", ::aName)
}

class TeamWithTraditions2 : Captain {
  override var captain: String
    by Delegates.vetoable("Adam") {
      _, old, new ->
        if (new.startsWith("A")) {
          trace("$old -> $new")
          true
        } else {
          trace("Name must start with 'A'")
          false
        }
    }
}

fun main() {
  listOf(
    TeamWithTraditions(),
   

 TeamWithTraditions2()
  ).forEach {
    it.captain = "Amanda"
    it.captain = "Bill"
    it.captain eq "Amanda"
  }
  trace eq """
    Adam -> Amanda
    Name must start with 'A'
    Adam -> Amanda
    Name must start with 'A'
  """
}

Delegates.vetoable() 接受两个参数:属性的初始值,以及一个 onChange() 函数,这在这个例子中是 ::aNameonChange() 接受三个参数:property: KProperty<*>,属性当前持有的 old 值,以及放置在属性中的 new 值。该函数返回一个 Boolean,指示更改是否成功或被阻止。

TeamWithTraditions2 使用了一个 lambda 定义了 Delegates.vetoable(),而不是函数 aName()

properties.Delegates 中剩余的工具是 notNull(),它生成一个必须在读取之前初始化的属性:

// DelegationTools/NeverNull.kt
package delegationtools
import atomictest.*
import kotlin.properties.Delegates

class NeverNull {
  var nn: Int by Delegates.notNull()
}

fun main() {
  val non = NeverNull()
  capture {
    non.nn
  } eq "IllegalStateException: Property " +
    "nn should be initialized before get."
  non.nn = 11
  non.nn eq 11
}

在为 nn 分配值之前尝试读取 non.nn 会产生异常。在为 nn 分配值之后,可以成功读取它。

练习和解答可在 www.AtomicKotlin.com 找到。