Kotlin学习之-5.2 属性和成员
定义属性
Kotlin中,类可以有属性。它们既可以用var
关键字定义成变量,也可以用val
关键字定义成只读量。
class Address {
var name: String = ...
var street: String = ...
var city: String = ...
var state: String? = ...
var zip: String = ...
}
使用一个属性,只要简单的用它的名字引用即可,就像Java中的成员一样。
fun copyAddress(address: Address): Address {
val result = Address() // Kotlin中没有'new' 关键字
result.name = address.name
result.street = address.street
//...
return result
}
Getter 和Setter
定义属性完整语法结构如下:
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
构造器、getter 和setter都是可选项。如果属性的类型可以从构造器中推断出来的话,那么属性的类型也是可选的。示例如下:
var allByDefault: Int? // error: 需要显式的构造器。 有默认的getter 和setter
var initialized = 1 // 类型是Int。 有默认的getter 和setter
只读属性的语法和变量属性的的语法有两点不同: 定义是用val
而不是var
,并且没有setter 方法
val simple: Int? // 类型是Int,默认的getter方法,必须在构造函数中初始化。
val inferredType = 1 // 类型是Int, 默认的getter方法。
我们可以在属性定义时自定义getter方法,和普通的函数非常像。示例如下:
val isEmpty: Boolean
get() = this.size == 0
自定义setter方法示例如下:
var stringRepresentation: String
get() = this.toString()
set(value) {
setDataFromString(value) // 解析字符串并且赋值给其他属性
}
一般情况,setter方法的参数名是value
,但是也可以选择一个其他名字。
从Kotlin v1.1开始,如果属性类型可以从getter方法中推断出来,那么定义的时候可以省略属性类型。
val isEmpty() get() = this.size == 0 // 推断的类型Boolean
如果要更改函数的可见性或者添加注解,但是不需要干煸默认的实现,可以定义访问函数但不定义函数主体。
var setterVisibility: String = "abc"
private set
var setterWithAnnotation: Any? = null
@inject set
Backing Fields
Kotlin中,类没有成员。但是有时候使用自定义访问函数时也需要一个’backing field’。 在这些用法中,Kotlin提供一个自动的’backing field’, 它可以用field
关键字来访问。
var counter = 0
set(value) {
if (value >= 0) field = value
}
field
描述符只能用在属性的访问。
属性会产生一个’backing field’,如果属性有至少一个默认实现的访问函数,或者自定义的访问函数使用了field
描述符来访问这个属性。
例如,下面的例子中没有没有’backing field’
val isEmpty: Boolean
get() = this.size == 0
Backing Properties
如果你需要做的事情不符合隐式backing field模式,那么可以使用backing property
private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
get() {
if (_table == null) {
_table = HashMap() // 类型参数是推断出来的
}
return _table ?: throw AssertionError("Set to null by another thread")
}
总的来说,这和Java是一样的,使用默认的getter和setter方法访问私有的属性会被优化,从而没有函数调用的负担。
编译期常量
属性的值在编译期就确定的话,用const
修饰符来表示成编译期常量。这样的属性需要满足如下要求。
- 顶级的或者是一个
object
的成员 - 是用
String
类型或者基础类型初始化的 - 没有自定义getter方法
这样的属性可以在注解中使用
const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"
@Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { ... }
延迟初始化的属性
一般情况,被定义成非空的属性必须在构造函数中初始化。但是,这样通常不太方便。例如,属性可以在依赖注入的时候被初始化,或者在单元测试的setup方法中初始化。 在这种情况,就不能再构造函数中提供一个非空的初始化器,但是你仍然想要在引用属性的时候避免空指针检查。
处理这种情况,需要用lateinit
描述符来标识这个属性。
public class MyTest {
lateinit var subject: TestSubject
@SetUp fun setup() {
subject = TestSubject()
}
@Test fun test() {
subject.method()
}
}
延迟初始化lateinit
描述符只能用在var
属性,并且属性不能有自定义的getter和setter。属性的类型必须是非空的,并且不能是基础类型。
在属性初始化之前,访问一个延迟初始化lateinit
的属性,会抛出一个特殊的异常,该异常表明属性在访问的时候,还没有初始化。
代理属性
最常用的属性就是简单地读取或者写入backing field。 另外,使用自定义的getter和setter可以实现属性任意的操作。 除了这两种情况,还有一些常见的用法。例如,延迟初始化的值,用一个给定的关键字从一个map中获取对应的值,访问数据库,通知监听器等等。
这些常用的行为和操作可以通过库的方式来实现。这可以使用代理属性的方式来实现。
PS,我会坚持把这个系列写完,有问题可以留言交流,也关注专栏Kotlin for Android Kotlin安卓开发