備用字段咽瓷?
幕后字段设凹?
支持字段?
后端域變量茅姜?
翻譯這么多種闪朱,其實(shí)都是一個(gè)意思 Backing Field。
找到一些關(guān)于他的描述:
Kotlin中的類不能有field钻洒。但是奋姿,有時(shí)在使用自定義訪問器時(shí)必須有一個(gè) backing field 。為此素标,Kotlin提供了一個(gè)自動(dòng)backing field称诗,可以使用 field 標(biāo)識(shí)符來(lái)訪問。
Backing field is an autogenerated field for any property which can only be used inside the accessors(getter or setter) and will be present only if it uses the default implementation of at least one of the accessors, or if a custom accessor references it through the field identifier.This backing field is used to avoid the recursive call of an accessor which ultimately prevents the StackOverflowError.
幕后字段是一個(gè)自動(dòng)生成的字段头遭,它僅僅可以被用在擁有至少一個(gè)默認(rèn)訪問器 (getter寓免、setter) 、或者在自定義訪問器中通過 field 標(biāo)識(shí)符修飾的屬性中计维。幕后字段可以避免訪問器的自遞歸而導(dǎo)致程序崩潰的 StackOverflowError 異常袜香。
那么這里有一些關(guān)于field的特點(diǎn):
- field標(biāo)識(shí)符只允許在屬性的訪問器函數(shù)內(nèi)使用。
- 如果你顯式地引用或者使用默認(rèn)的訪問器實(shí)現(xiàn)鲫惶,編譯器會(huì)為屬性生成field蜈首。
- 如果你提供了一個(gè)自定義的訪問器實(shí)現(xiàn)并且沒有使用field,將不會(huì)生成field剑按。
- 訪問屬性的方式不依賴于它是否含有field疾就。
在setter函數(shù)體中,使用了特殊標(biāo)記符field來(lái)訪問字段的值
在getter中艺蝴,只能讀取該值猬腰。
在setter中,既能讀取也能修改它猜敢。
//使用field關(guān)鍵字
public var fieldProp = ""
get() = field
set(value) {
field = value
}
//自定義不使用field 不生成:
val isEmpty: Boolean
get() = this.size == 0
//默認(rèn)訪問器 生成:
val Foo.bar = 1
如果在類中定義一個(gè)成員變量姑荷,kotlin將自動(dòng)生成默認(rèn)的setter/getter方法盒延。
kotlin聲明get/set的方式為
var name: String? = null
set(value) {
field = value
}
get() = field
這里使用了field,如果不使用field鼠冕,會(huì)這么寫:
var name: String? = null
set(value) {
name = value
}
get() = name
但是這么寫添寺,對(duì)這個(gè)屬性進(jìn)行賦值并取值時(shí)出現(xiàn)了一些問題:
在setter方法中對(duì)屬性進(jìn)行賦值和取值時(shí),會(huì)調(diào)用自身懈费,出現(xiàn)了遞歸調(diào)用计露。
這個(gè)時(shí)候field就可以解決這個(gè)問題了,backing field的作用域在當(dāng)前屬性的setter/getter方法中憎乙,以中間變量的形式票罐,來(lái)解決了遞歸問題。
在拓展屬性中
在拓展屬性中泞边,是沒有field這個(gè)字段的
轉(zhuǎn)換一下寫法
按照正常的寫法该押,這里報(bào)錯(cuò)。說明拓展屬性沒有field阵谚。
如果把這個(gè)拓展常量變成屬性蚕礼,會(huì)提示屬性必須初始化:
那我們直接初始化試試
直接賦值進(jìn)行初始化又報(bào)錯(cuò)了: 拓展屬性不能初始化,因?yàn)樗麤]有支持字段 (backing field)梢什。
為什么拓展屬性不能初始化奠蹬?這個(gè)問題可以轉(zhuǎn)換為 為什么拓展屬性沒有支持字段?
官方說:
實(shí)際上嗡午,擴(kuò)展并不會(huì)真正地往類中插入成員變量罩润。因此,我們沒有一個(gè)有效的方式讓一個(gè)擴(kuò)展屬性擁有backing field翼馆,這就是擴(kuò)展屬性不允許被初始化的原因割以。
那么拓展屬性到底是什么?
Kotlin 可以對(duì)一個(gè)類的屬性和方法進(jìn)行擴(kuò)展应媚,且不需要繼承或使用裝飾器模式严沥。擴(kuò)展是一種靜態(tài)行為,對(duì)被擴(kuò)展的類代碼本身不會(huì)造成任何影響中姜。
因?yàn)椴粫?huì)對(duì)類的代碼本身造成任何影響消玄,所以擴(kuò)展不會(huì)真正的往類中插入成員變量。
field的作用域是當(dāng)前屬性的訪問器丢胚,而當(dāng)前屬性是與某個(gè)類的實(shí)例對(duì)應(yīng)的翩瓜,所以類中并沒有這個(gè)屬性,也就沒有一個(gè)有效的方式讓這個(gè)屬性擁有backing field了携龟。
- 有field和沒有field的屬性有什么區(qū)別兔跌?
訪問屬性的方式不依賴于它是否含有field,如果你顯式地引用或者使用默認(rèn)的訪問器實(shí)現(xiàn)峡蟋,編譯器會(huì)為屬性生成field坟桅。如果你提供了一個(gè)自定義的訪問器實(shí)現(xiàn)并且沒有使用field华望,支持字段將不會(huì)被呈現(xiàn)出來(lái)。
有時(shí)候不需要修改訪問器的默認(rèn)實(shí)現(xiàn)仅乓,但是需要修改它的可見性:
class LengthCounter {
var counter: Int = 0
private set //這個(gè)屬性不能在所在類外部被修改
fun addWord(word: String) {
counter += word.length
}
}
在接口中
在接口中赖舟,可以包含具有setter/getter的屬性,只要他們沒有引用一個(gè)backing field夸楣,
backing field需要在接口中存儲(chǔ)狀態(tài)宾抓,而這是不被允許的
支持屬性:功能與backing field相似,能達(dá)到相同的效果:
private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
get() {
if (_table == null) {
_table = HashMap() // 參數(shù)類型是自動(dòng)推導(dǎo)
}
return _table ?: throw AssertionError("Set to null by another thread")
}
參考
https://medium.com/@agrawalsuneet/backing-field-in-kotlin-bd9c2d5b6da5
https://juejin.im/post/5a79c053f265da4e6e2bad37
https://blog.csdn.net/Strange_Monkey/article/details/82707242