系列文章全部為本人的學(xué)習(xí)筆記,若有任何不妥之處炬称,隨時(shí)歡迎拍磚指正伞广。如果你覺得我的文章對你有用拣帽,歡迎關(guān)注我,我們一起學(xué)習(xí)進(jìn)步嚼锄!
Kotlin學(xué)習(xí)筆記(1)- 環(huán)境配置
Kotlin學(xué)習(xí)筆記(2)- 空安全
Kotlin學(xué)習(xí)筆記(3)- 語法
Kotlin學(xué)習(xí)筆記(4)- 流程控制
Kotlin學(xué)習(xí)筆記(5)- 類
Kotlin學(xué)習(xí)筆記(6)- 屬性
Kotlin學(xué)習(xí)筆記(7)- 接口
Kotlin學(xué)習(xí)筆記(8)- 擴(kuò)展
Kotlin學(xué)習(xí)筆記(8)- 擴(kuò)展(續(xù))
Kotlin學(xué)習(xí)筆記(9)- 數(shù)據(jù)類
Kotlin學(xué)習(xí)筆記(10)- 泛型
Kotlin學(xué)習(xí)筆記(11)- 內(nèi)部類和嵌套類
Kotlin學(xué)習(xí)筆記(12)- 委托
Kotlin學(xué)習(xí)筆記(13)- 函數(shù)式編程
Kotlin學(xué)習(xí)筆記(14)- lambda
一减拭、屬性聲明
一般的說,類是屬性和邏輯的集合区丑。我們用方法封裝和處理邏輯拧粪,用變量聲明屬性。所以可以大言不慚的說一句沧侥,屬性聲明在類中是必不可少的可霎。在《Kotlin學(xué)習(xí)筆記(3)- 語法》中我們介紹過,kotlin中的屬性聲明有兩種:var
聲明普通變量宴杀,val
聲明只讀變量(暨final
類型)癣朗。其實(shí)還有幾點(diǎn)需要說明。
-
類中聲明的屬性旺罢,一定要初始化旷余,否則會編譯錯(cuò)誤绢记。除非你對屬性使用了
abstract
進(jìn)行修飾。var name: String = "" abstract var size : Int
-
屬性聲明中的屬性類型在大部分情況下是可以省略的正卧。這里說的大部分是指我們的最多的使用情況蠢熄,也就是默認(rèn)的使用場景。
var name = ""
那什么時(shí)候是不能省略的呢炉旷?這還要說到另一個(gè)問題护赊,那就是屬性的修飾符,和方法類似的砾跃,屬性也有多種修飾符骏啰。
public // 默認(rèn)的修飾符。全部可見抽高,在屬性被初始化時(shí)判耕, // 如果可以根據(jù)屬性的值推斷出屬性類型,則可省略類型 protected // 在本身和子類中可見翘骂,如果可以根據(jù)屬性的值推斷出屬性類型壁熄,則可省略類型 private // 只在本身可見,如果可以根據(jù)屬性的值推斷出屬性類型碳竟,則可省略類型 abstract // 在自身不初始化草丧,需要子類進(jìn)行初始化,不能省略類型莹桅。
-
類中的屬性昌执,用
.
進(jìn)行訪問例如上面的name
屬性,我們可以這樣進(jìn)行讀寫操作var person = Person() person.name = "jck" // 寫 Log.d("log", person.name) // 讀
二诈泼、getter和setter
-
默認(rèn)方法
上面的例子可以看到懂拾,我們可以直接調(diào)用
person.name
對屬性進(jìn)行讀寫操作,這種操作我們在java中也見過铐达,是對類中的public
屬性進(jìn)行操作岖赋。而在kotlin中,name
屬性雖然也是public
的瓮孙,但是意義和java中是完全不同的唐断,這里的讀寫其實(shí)是對get
和set
函數(shù)的隱式調(diào)用,而get
和set
函數(shù)是默認(rèn)實(shí)現(xiàn)的杭抠,而顯式的寫出來則是這樣// 非源碼脸甘,而是根據(jù)自己理解寫出的。這里的field下面會說 var name: String = "" get() = field // get set(value) { // set field = value }
也就是說祈争,之前我們說到的
person.name
的讀寫操作斤程,其實(shí)是對get
和set
方法的訪問角寸,而并不是像java中的直接對屬性的操作菩混,保證了屬性的閉合性忿墅。完整的聲明如下:var <propertyName>: <PropertyType> [= <property_initializer>] [<getter>] [<setter>]
其中initializer, getter 和 setter都是可選的。var是允許有g(shù)etter 和 setter方法沮峡,val不允許有setter方法疚脐。如果屬性值的數(shù)據(jù)類型可以通過編譯器自動推斷,或者在getter和setter方法中并沒有對屬性做特殊處理邢疙,這些方法都可以省略棍弄。
-
訪問權(quán)限
在java中我們可以根據(jù)需要決定是否實(shí)現(xiàn)屬性的
get
和set
方法,kotlin中自然也有針對針對這種需求的實(shí)現(xiàn)疟游。首先屬性都是有get
和set
方法的呼畸,當(dāng)我們不想對外公開某個(gè)方法時(shí),可以使用修飾符private
實(shí)現(xiàn)颁虐,例如var name: String = "" private set
但是這種方式只適用于
set
方法蛮原,get
的訪問權(quán)限默認(rèn)是和屬性一致的,下面的使用會報(bào)編譯錯(cuò)誤var name: String = "" private get // 編譯錯(cuò)誤另绩,get的訪問權(quán)限和屬性一致
如果你們有一個(gè)需求儒陨,要求對某個(gè)屬性只可寫,不可讀笋籽,那么請用方法
fun
實(shí)現(xiàn)吧…… -
自定義getter和setter
上面說了
get
和set
的默認(rèn)實(shí)現(xiàn)蹦漠,那么就再來說說自定義實(shí)現(xiàn),當(dāng)然车海,像上面說過的笛园,var
有set
和get
,val
只有get
// 自定義get var size: Int = 2 get() = if (field > 10) 15 else 0 // 調(diào)用 var pf = PropertiesFields() pf.size = 5 Log.d("text", "size : ${pf.size}") pf.size = 20 Log.d("text", "size : ${pf.size}") // 輸出 size : 0 size : 15 // 自定義set var size: Int = 2 set(value) { field = if (value > 10) 15 else 0 } // 調(diào)用和輸出同上
上面的代碼很簡單侍芝,就是一個(gè)
if
表達(dá)式 喘沿,就不多解釋了。其實(shí)kotlin中的getter
和setter
理解起來很簡單竭贩,就像java中的所有屬性都是private
并且實(shí)現(xiàn)了getter
和setter
蚜印,其他的像權(quán)限和自定義問題,都和java中類似留量。
三窄赋、后端變量(Backing Fields)
看大神是這么翻譯Backint Fields的,那我們也這么叫好了楼熄。這里就要說到上面提到的field
了忆绰,在kotlin的getter
和setter
是不允許本身的局部變量的,因?yàn)閷傩缘恼{(diào)用也是對get
的調(diào)用可岂,因此會產(chǎn)生遞歸错敢,造成內(nèi)存溢出。
var count = 1
var size: Int = 2
set(value) {
Log.e("text", "count : ${count++}")
size = if (value > 10) 15 else 0
}
kotlin為此提供了一種我們要說的后端變量,也就是field
稚茅。編譯器會檢查函數(shù)體纸淮,如果使用到了它,就會生成一個(gè)后端變量亚享,否則就不會生成咽块。我們在使用的時(shí)候,用field代替屬性本身進(jìn)行操作欺税。
var size: Int = 2
val isEmpty: Boolean
get() = this.size == 0
這是官方文檔的一個(gè)例子侈沪,在訪問屬性值isEmpty時(shí),并不會生成后端變量晚凿。
這里我有一個(gè)疑惑亭罪。我們說過類屬性是一定要初始化的,但是我在編譯這個(gè)例子的時(shí)候確實(shí)是沒問題的歼秽。我又嘗試著將
isEmpty
改為普通變量皆撩,然后就編譯出錯(cuò),希望朋友能幫我解惑哲银,謝謝扛吞。var size: Int = 2 var isEmpty: Boolean // 這樣就編譯報(bào)錯(cuò) get() = this.size == 0
四、后端屬性
如果上面的方案都不符合你的需求荆责,那么可以試試“后端屬性”(backing property)的方法滥比,它實(shí)際上也是隱含試的對屬性值的初始化聲明,避免了空指針做院。
private var _table: Map<String, Int>? = null
val table: Map<String, Int>
get() {
if (_table == null)
_table = HashMap() // Type parameters are inferred
return _table ?: throw AssertionError("Set to null by another thread")
}
從各種角度看盲泛,這和在Java中定義Bean
屬性的方式一樣。因?yàn)樵L問私有的屬性的getter
和setter
函數(shù)键耕,會被編譯器優(yōu)化成直接反問其實(shí)際字段寺滚。
五、編譯器常數(shù)值
如果在編譯期間屈雄,屬性值就能被確定村视,該類屬性值使用const 修飾符,將屬性標(biāo)記為編譯期常數(shù)值(compile timeconstants). 這類屬性必須滿足以下所有條件:
- 必須是頂級屬性酒奶,或者是一個(gè)object的成員
- 值被初始化為 String 類型蚁孔,或基本類型(primitive type)
- 不存在自定義的取值方法
六、延遲初始化屬性(lateinit)
我們說過惋嚎,在類內(nèi)聲明的屬性必須初始化杠氢,如果設(shè)置非NULL的屬性,應(yīng)該將此屬性在構(gòu)造器內(nèi)進(jìn)行初始化另伍。假如想在類內(nèi)聲明一個(gè)NULL屬性鼻百,在需要時(shí)再進(jìn)行初始化(最典型的就是懶漢式單例模式),與Kotlin的規(guī)則是相背的,此時(shí)我們可以聲明一個(gè)屬性并延遲其初始化温艇,此屬性用lateinit修飾符修飾因悲。
// 延遲初始化聲明
lateinit var late : String
fun initLate(){
late = "I am late"
}
// 先調(diào)用方法,再調(diào)用屬性
var pf = PropertiesFields()
pf.initLate()
Log.d("text", pf.late)
// 輸出
I am late
需要注意的是中贝,我們在使用的時(shí)候,一定要確保屬性是被初始化過的臼朗,通常先調(diào)用初始化方法邻寿,否則會有異常。