屬性和域
什么是屬性文兢?
實(shí)際是指getter和setter方法扑毡,雖然和字段是兩個(gè)概念,姑且可以理解為有g(shù)etter和setter方法的字段窟赏。
如何聲明?
完整語(yǔ)法:
var 屬性名: <屬性類(lèi)型> [= <初始化器>]
[<getter>]
[<setter>]
其中的初始化器别凤、getter和setter方法是可選的饰序。如果沒(méi)有自定義getter和setter的實(shí)現(xiàn),就會(huì)使用默認(rèn)的规哪。
以下兩種情況屬性類(lèi)型也可省略:
- 可以通過(guò)初始化器推斷出來(lái)
- 被聲明的屬性會(huì)覆蓋基類(lèi)中的屬性求豫,并且通過(guò)基類(lèi)中屬性的類(lèi)型推動(dòng)出來(lái)
兩種屬性:
只讀屬性:val
關(guān)鍵字聲明
可變屬性:var
關(guān)鍵字聲明
訪問(wèn)屬性:
訪問(wèn)屬性使用點(diǎn)操作符,對(duì)象名.屬性名
诉稍,如:
var person = Person()
person.name = "張三"
和oc一樣蝠嘉,點(diǎn)操作符內(nèi)部的實(shí)現(xiàn)也是調(diào)用getter或setter訪問(wèn)器。并且在Kotlin中訪問(wèn)Java的屬性時(shí)杯巨,仍然可以用點(diǎn)語(yǔ)法訪問(wèn)Java對(duì)象中的屬性蚤告。
如果要修改訪問(wèn)器的可見(jiàn)性或添加注解,但又不需要修改默認(rèn)實(shí)現(xiàn)服爷,你可以重新定義方法杜恰,但不定義它的實(shí)現(xiàn),這樣仍然是使用默認(rèn)實(shí)現(xiàn)仍源。如:
var setterVisibility: String = "abc"
private set // 設(shè)值方法的可見(jiàn)度為 private, 并使用默認(rèn)實(shí)現(xiàn)
var setterWithAnnotation: Any? = null
@Inject set // 對(duì)設(shè)值方法添加 Inject 注解
屬性的后端域變量(Backing Field)
Kotlin類(lèi)不能擁有域變量(也就是Java中的成員變量)心褐,但是使用訪問(wèn)器時(shí)又需要這種域變量,所以Kotlin提供了后端域變量笼踩,可以用field
標(biāo)識(shí)符來(lái)訪問(wèn)逗爹。
如:
var counter = 0 // 初始化給定的值將直接寫(xiě)入后端域變量中
set(value) {
if (value >= 0) field = value
}
}
field
就代表編譯器自動(dòng)生成的后端域變量,并且只允許在屬性訪問(wèn)器中使用
什么情況下會(huì)生成后端域變量嚎于?
- 訪問(wèn)器中任一個(gè)使用默認(rèn)實(shí)現(xiàn)
- 自定義的訪問(wèn)器中通過(guò)
field
關(guān)鍵字訪問(wèn)屬性
下面這種情況下就不會(huì)生成后端域變量:
val isEmpty: Boolean
get() = this.size() == 0 //自定義訪問(wèn)器沒(méi)有使用field關(guān)鍵字訪問(wèn)屬性
為什么不用this
關(guān)鍵字訪問(wèn)屬性呢掘而?
試想下,this
代表當(dāng)前對(duì)象于购,而上面說(shuō)過(guò)通過(guò)點(diǎn)操作符袍睡,訪問(wèn)屬性時(shí),實(shí)際上是調(diào)用了訪問(wèn)器肋僧,在訪問(wèn)器內(nèi)部實(shí)現(xiàn)中再調(diào)用訪問(wèn)器就會(huì)出現(xiàn)無(wú)限遞歸女蜈。所以持舆,通過(guò)Backing Filed可以避免這個(gè)問(wèn)題色瘩。
后端屬性(Backing Property)
如果你的操作是想訪問(wèn)屬性內(nèi)部的屬性伪窖,或者集合里面的元素,而不是屬性本身居兆,那么你可以聲明這個(gè)屬性為private
的(后端屬性)覆山,然后定義個(gè)public
的屬性并自定義其訪問(wèn)器,在自定義的訪問(wèn)器內(nèi)部訪問(wèn)后端屬性泥栖。如:
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")
}
其實(shí)也沒(méi)有什么神秘的,其實(shí)就像Java中我們通常寫(xiě)的吧享,在訪問(wèn)私有屬性時(shí)魏割,在自定義訪問(wèn)器中添加些限制,避免取到或設(shè)置非法的值一個(gè)道理钢颂。只是在Kotlin中取了個(gè)高大上的名字钞它。
編譯器常數(shù)值
值在編譯期就能確定的屬性,用const
關(guān)鍵字修飾殊鞭。滿足以下條件的屬性可以標(biāo)記為編譯器常數(shù)值:
- 必須是頂級(jí)屬性或是
object
的成員 - 值必須是
String
或基本類(lèi)型 - 不能自定義取值方法
如:
const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"
編譯器常數(shù)值可以用在注解內(nèi):
@Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { ... }
延遲初始化屬性
通常屬性被聲明為非null
類(lèi)型就必須就地初始化遭垛。但是,這種限制在很多情況下是不方便的操灿,比如锯仪,聲明的屬性通過(guò)依賴注入的方式來(lái)初始化。這種情況下趾盐,你就可以使用lateinit
關(guān)鍵字來(lái)修飾聲明的屬性庶喜。
如:
public class MyTest {
lateinit var subject: TestSubject
@SetUp fun setup() {
subject = TestSubject()
}
@Test fun test() {
subject.method() // 直接訪問(wèn)屬性
}
}
使用限制:
- 只能用于
var
屬性 - 只能用于類(lèi)體內(nèi)聲明的屬性
- 屬性不能有自定義訪問(wèn)器
- 屬性必須是非
null
的 - 屬性不能是基本類(lèi)型