属性和域
什么是属性?
实际是指getter和setter方法,虽然和字段是两个概念,姑且可以理解为有getter和setter方法的字段。
如何声明?
完整语法:
var 属性名: <属性类型> [= <初始化器>]
[<getter>]
[<setter>]
其中的初始化器、getter和setter方法是可选的。如果没有自定义getter和setter的实现,就会使用默认的。
以下两种情况属性类型也可省略:
- 可以通过初始化器推断出来
- 被声明的属性会覆盖基类中的属性,并且通过基类中属性的类型推动出来
两种属性:
只读属性:val
关键字声明
可变属性:var
关键字声明
访问属性:
访问属性使用点操作符,对象名.属性名
,如:
var person = Person()
person.name = "张三"
和oc一样,点操作符内部的实现也是调用getter或setter访问器。并且在Kotlin中访问Java的属性时,仍然可以用点语法访问Java对象中的属性。
如果要修改访问器的可见性或添加注解,但又不需要修改默认实现,你可以重新定义方法,但不定义它的实现,这样仍然是使用默认实现。如:
var setterVisibility: String = "abc"
private set // 设值方法的可见度为 private, 并使用默认实现
var setterWithAnnotation: Any? = null
@Inject set // 对设值方法添加 Inject 注解
属性的后端域变量(Backing Field)
Kotlin类不能拥有域变量(也就是Java中的成员变量),但是使用访问器时又需要这种域变量,所以Kotlin提供了后端域变量,可以用field
标识符来访问。
如:
var counter = 0 // 初始化给定的值将直接写入后端域变量中
set(value) {
if (value >= 0) field = value
}
}
field
就代表编译器自动生成的后端域变量,并且只允许在属性访问器中使用
什么情况下会生成后端域变量?
- 访问器中任一个使用默认实现
- 自定义的访问器中通过
field
关键字访问属性
下面这种情况下就不会生成后端域变量:
val isEmpty: Boolean
get() = this.size() == 0 //自定义访问器没有使用field关键字访问属性
为什么不用this
关键字访问属性呢?
试想下,this
代表当前对象,而上面说过通过点操作符,访问属性时,实际上是调用了访问器,在访问器内部实现中再调用访问器就会出现无限递归。所以,通过Backing Filed可以避免这个问题。
后端属性(Backing Property)
如果你的操作是想访问属性内部的属性,或者集合里面的元素,而不是属性本身,那么你可以声明这个属性为private
的(后端属性),然后定义个public
的属性并自定义其访问器,在自定义的访问器内部访问后端属性。如:
private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
get() {
// 这里的操作不希望属于 backing filed,
if (_table == null) {
_table = HashMap()
}
return _table ?: throw AssertionError("Set to null by another thread")
}
其实也没有什么神秘的,其实就像Java中我们通常写的,在访问私有属性时,在自定义访问器中添加些限制,避免取到或设置非法的值一个道理。只是在Kotlin中取了个高大上的名字。
编译器常数值
值在编译期就能确定的属性,用const
关键字修饰。满足以下条件的属性可以标记为编译器常数值:
- 必须是顶级属性或是
object
的成员 - 值必须是
String
或基本类型 - 不能自定义取值方法
如:
const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"
编译器常数值可以用在注解内:
@Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { ... }
延迟初始化属性
通常属性被声明为非null
类型就必须就地初始化。但是,这种限制在很多情况下是不方便的,比如,声明的属性通过依赖注入的方式来初始化。这种情况下,你就可以使用lateinit
关键字来修饰声明的属性。
如:
public class MyTest {
lateinit var subject: TestSubject
@SetUp fun setup() {
subject = TestSubject()
}
@Test fun test() {
subject.method() // 直接访问属性
}
}
使用限制:
- 只能用于
var
属性 - 只能用于类体内声明的属性
- 属性不能有自定义访问器
- 属性必须是非
null
的 - 属性不能是基本类型