屬性和域
什么是屬性?
實際是指getter和setter方法,雖然和字段是兩個概念,姑且可以理解為有g(shù)etter和setter方法的字段。
如何聲明?
完整語法:
var 屬性名: <屬性類型> [= <初始化器>]
[<getter>]
[<setter>]
其中的初始化器、getter和setter方法是可選的。如果沒有自定義getter和setter的實現(xiàn),就會使用默認的。
以下兩種情況屬性類型也可省略:
- 可以通過初始化器推斷出來
- 被聲明的屬性會覆蓋基類中的屬性,并且通過基類中屬性的類型推動出來
兩種屬性:
只讀屬性:val
關(guān)鍵字聲明
可變屬性:var
關(guān)鍵字聲明
訪問屬性:
訪問屬性使用點操作符,對象名.屬性名
,如:
var person = Person()
person.name = "張三"
和oc一樣,點操作符內(nèi)部的實現(xiàn)也是調(diào)用getter或setter訪問器。并且在Kotlin中訪問Java的屬性時,仍然可以用點語法訪問Java對象中的屬性。
如果要修改訪問器的可見性或添加注解,但又不需要修改默認實現(xiàn),你可以重新定義方法,但不定義它的實現(xiàn),這樣仍然是使用默認實現(xiàn)。如:
var setterVisibility: String = "abc"
private set // 設(shè)值方法的可見度為 private, 并使用默認實現(xiàn)
var setterWithAnnotation: Any? = null
@Inject set // 對設(shè)值方法添加 Inject 注解
屬性的后端域變量(Backing Field)
Kotlin類不能擁有域變量(也就是Java中的成員變量),但是使用訪問器時又需要這種域變量,所以Kotlin提供了后端域變量,可以用field
標識符來訪問。
如:
var counter = 0 // 初始化給定的值將直接寫入后端域變量中
set(value) {
if (value >= 0) field = value
}
}
field
就代表編譯器自動生成的后端域變量,并且只允許在屬性訪問器中使用
什么情況下會生成后端域變量?
- 訪問器中任一個使用默認實現(xiàn)
- 自定義的訪問器中通過
field
關(guān)鍵字訪問屬性
下面這種情況下就不會生成后端域變量:
val isEmpty: Boolean
get() = this.size() == 0 //自定義訪問器沒有使用field關(guān)鍵字訪問屬性
為什么不用this
關(guān)鍵字訪問屬性呢?
試想下,this
代表當前對象,而上面說過通過點操作符,訪問屬性時,實際上是調(diào)用了訪問器,在訪問器內(nèi)部實現(xiàn)中再調(diào)用訪問器就會出現(xiàn)無限遞歸。所以,通過Backing Filed可以避免這個問題。
后端屬性(Backing Property)
如果你的操作是想訪問屬性內(nèi)部的屬性,或者集合里面的元素,而不是屬性本身,那么你可以聲明這個屬性為private
的(后端屬性),然后定義個public
的屬性并自定義其訪問器,在自定義的訪問器內(nèi)部訪問后端屬性。如:
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中我們通常寫的,在訪問私有屬性時,在自定義訪問器中添加些限制,避免取到或設(shè)置非法的值一個道理。只是在Kotlin中取了個高大上的名字。
編譯器常數(shù)值
值在編譯期就能確定的屬性,用const
關(guān)鍵字修飾。滿足以下條件的屬性可以標記為編譯器常數(shù)值:
- 必須是頂級屬性或是
object
的成員 - 值必須是
String
或基本類型 - 不能自定義取值方法
如:
const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"
編譯器常數(shù)值可以用在注解內(nèi):
@Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { ... }
延遲初始化屬性
通常屬性被聲明為非null
類型就必須就地初始化。但是,這種限制在很多情況下是不方便的,比如,聲明的屬性通過依賴注入的方式來初始化。這種情況下,你就可以使用lateinit
關(guān)鍵字來修飾聲明的屬性。
如:
public class MyTest {
lateinit var subject: TestSubject
@SetUp fun setup() {
subject = TestSubject()
}
@Test fun test() {
subject.method() // 直接訪問屬性
}
}
使用限制:
- 只能用于
var
屬性 - 只能用于類體內(nèi)聲明的屬性
- 屬性不能有自定義訪問器
- 屬性必須是非
null
的 - 屬性不能是基本類型