Kotlin Reference: Properties and Fields

Declaring Properties

  Kotlin 中的类可以具有属性。可以使用 var 关键字声明可变(Mutable)属性,或者使用 val 关键字声明只读(Read-Only)属性。

[code lang=”kotlin”]class Address {
var name: String = …
var street: String = …
var city: String = …
var state: String? = …
var zip: String = …
}[/code]

  直接通过属性名就可以引用属性,就像使用 Java 的字段一样:

[code lang=”kotlin”]fun copyAddress(address: Address): Address {
val result = Address() // there’s no ‘new’ keyword in Kotlin
result.name = address.name // accessors are called
result.street = address.street
// …
return result
}[/code]

Getters and Setters

  声明属性的完整语法为:

[code lang=”kotlin”]var &ltpropertyName&gt[: &ltPropertyType&gt] [= &ltproperty_initializer&gt]
[&ltgetter&gt]
[&ltsetter&gt][/code]

其中的初始化器(property_initializer)、getter 和 setter 都是可选的。如果属性类型能够通过初始化(或者 getter 的返回值类型,如下所示)推断出来,则属性类型也可以省略。

  例如:

[code lang=”kotlin”]var allByDefault: Int? // error: explicit initializer required, default getter and setter implied
var initialized = 1 // has type Int, default getter and setter[/code]

  声明只读属性的完整语法与可变属性有两点区别:只读属性使用 val 关键字而不是 var,且不允许有 setter:

[code lang=”kotlin”]val simple: Int? // has type Int, default getter, must be initialized in constructor
val inferredType = 1 // has type Int and a default getter[/code]

  我们可以在属性声明中编写自定义的访问器,就像常规的方法一样,如自定义 getter:

[code lang=”kotlin”]val isEmpty: Boolean
get() = this.size == 0[/code]

也可以自定义 setter:

[code lang=”kotlin”]var stringRepresentation: String
get() = this.toString()
set(value) {
setDataFromString(value) // parses the string and assigns values to other properties
}[/code]

按照惯例,setter 的参数名为 value,但你也可以选用任意的名称。

  从 Kotlin 1.1 开始,如果属性的类型可以从 getter 推断出来,就可以在声明属性时省略类型:

[code lang=”kotlin”]val isEmpty get() = this.size == 0 // has type Boolean[/code]

  如果想要在不修改默认实现的情况下,修改访问器的可见性或者添加注解,可以只定义访问器的名称而不给出实现:

[code lang=”kotlin”]var setterVisibility: String = “abc”
private set // the setter is private and has the default implementation

var setterWithAnnotation: Any? = null
@Inject set // annotate the setter with Inject[/code]

Backing Fields

  Kotlin 的类没有字段,但有时在使用自定义访问器时,需要有一个支持字段(Backing Field)。为此,Kotlin 提供了自动创建的支持字段,可以使用 field 标识符来访问:

[code lang=”kotlin”]var counter = 0 // the initializer value is written directly to the backing field
set(value) {
if (value >= 0) field = value
}[/code]

field 标识符只能在属性的访问器中使用。

  只有当属性使用了一个以上默认实现的访问器,或者自定义访问器使用 field 标识符引用了支持字段时,才会为该属性创建支持字段。

  例如,下面的情况就不会创建支持字段:

[code lang=”kotlin”]val isEmpty: Boolean
get() = this.size == 0[/code]

Backing Properties

  如果“隐式支持字段”的方案不能满足需要,那么还可以使用支持属性。

[code lang=”kotlin”]private var _table: Map? = null
public val table: Map
get() {
if (_table == null) {
_table = HashMap() // Type parameters are inferred
}
return _table ?: throw AssertionError(“Set to null by another thread”)
}[/code]

从各方面来看,上面代码就像 Java(声明私有字段并提供 getter)。使用私有属性和默认的 getter、setter 是经过优化的,不会引入函数调用的开销。

Compile-Time Constants

  如果属性的值在编译期间就是已知的,可以使用 const 关键字把它标记为编译时常量(Compile Time Constant),此类属性需要满足如下要求:

  • 为顶层属性,或者是 object 的成员
  • 初始化为 String 或基本类型
  • 没有自定义 getter

  编译时常量可以在注解中使用:

[code lang=”kotlin”]const val SUBSYSTEM_DEPRECATED: String = “This subsystem is deprecated”

@Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { … }[/code]

Late-Initialized Properties

  通常来说,具有非空(Non-Null)类型的属性必须在构造器中初始化,但这通常不是很方便,比如属性可以通过依赖注入或单元测试的配置方法完成初始化。在这些情况下,并不能在构造器中提供非空初始化,但又希望在类中引用属性时避免空引用检查。

  为了处理这种情况,可以使用 lateinit 标识符来标记属性:

[code lang=”kotlin”]public class MyTest {
lateinit var subject: TestSubject

@SetUp fun setup() {
subject = TestSubject()
}

@Test fun test() {
subject.method() // dereference directly
}
}[/code]

lateinit 只能用于在类中(而不是主构造器中)定义的 var 属性,且该属性不能具有自定义的 getter 或 setter。该属性的类型必须为非空,且不能是基本类型(Primitive Type)。

  在 lateinit 属性初始化前访问该属性时,会抛出一个特殊的异常,指明当前访问的属性尚未初始化。

Overriding Properties

  见 覆盖属性

Delegated Properties

  最常见的一类属性仅从支持字段中进行读取(或写入),而自定义 getter 和 setter 可以实现属性的任意行为。在这两种场景之间,还有其他常见的特定模式,比如延迟初始化值、根据 Key 读取 Map,读取数据库、在被访问时通知监听者等。

  这些行为可以使用委托属性(Delegated Property)以库的形式实现。