在前面的章節中,詳細的為大家講解到了Kotlin
中對類的類的定義、使用、初始化
、初始化、類繼承
等內容,但是在一個類中,幾乎上是不可能不出現屬性與欄位(field)
的,這一篇文章就為大家奉上Kotlin
中屬性與欄位
的定義、使用及高階操作等。如果您目前對Kotlin
中的類沒有一個認知的話,請參見Kotlin——中級篇(一):類(class)詳解.
目錄
一、屬性的基礎使用
一個類中是可以存在屬性的,一個屬性可以用
val
或者var
修飾。
- 用
val
修飾符修飾的屬性是隻讀的,即不能被修改,只可使用- 用
val
修飾符修飾的屬性是可讀寫的,即能用能改
例:
class Mime{
val id : String = "123"
var name : String? = "kotlin"
var age : Int? = 22
var sex : String? = "男"
var weight : Float = 120.3f
private var test : String = ""
get() = "123"
set(value){field = value}
}
fun main(args: Array<String>) {
val mime = Mime()
println("id = ${mime.id} \t name = ${mime.name} \t age = ${mime.age}
\t sex = ${mime.sex} \t weight = ${mime.weight}")
}
複製程式碼
輸出結果為:
id = 123 name = kotlin age = 22 sex =男 weight = 120.3
複製程式碼
注意:
`val`關鍵字為只讀,`var`為可讀寫。這裡舉例說明:
複製程式碼
例: 還是上面的例子
// id是隻讀的,其他的屬性是可讀寫的
mime.id = "123456" // 編輯器直接會報錯
mime.name = "kotlin2" // 可以,因為是var修飾的
...
複製程式碼
二、Getter()與Setter()
getter()
對應Java
中的get()函式
,setter()
對應Java
中的set()函式
。不過注意的是,不存在Getter()與Setter()
的,這只是Kotlin
中的叫法而已,真是的寫法,還是用get()、set()
。可以看下面的例子。- 在
Kotlin
中,普通的類中一般是不提供getter()
與setter()
函式的,因為在普通的類中幾乎用不到,這一點和Java
是相同的,但是Java
中在定義純粹的資料類時,會用到get()
與set()
函式,但是Kotlin
專門這種情況定義了資料類
,這個特徵。而資料類
是系統已經為我們實現了get()
和set()
函式。
這裡為大家演示getter()
與setter()
class Test{
/*
* getter 和 setter是可選的
*/
// 當用var修飾時,必須為屬性賦預設值(特指基本資料型別,因為自定義的型別可以使用後期初始化屬性,見後續) 即使在用getter()的情況下,不過這樣寫出來,不管我們怎麼去改變其值,其值都為`123`
var test1 : String = ""
get() = "123"
set(value){field = value}
// 用val修飾時,用getter()函式時,屬性可以不賦預設值。但是不能有setter()函式。
val test2 : String
get() = "123" // 等價於:val test2 : String = "123"
}
複製程式碼
注意: 請認真看上面的註釋......
在上面的程式碼中出現了set(value){field = value}
這樣的一句程式碼。其中value
是Koltin
寫setter()
函式時其引數的約定俗成的習慣。你也可以換成其他的值。而field
是指屬性本身。
2.1、自定義
這裡講解屬性的自定義getter()
與setter()
。由上面可知,使用val
修飾的屬性,是不能有setter()
的。而使用var
修飾的屬性可以同時擁有自定義的getter()
與setter()
。通過兩個例項來說明:
例1:用val
修飾的屬性自定義情況
class Mime{
// size屬性
private val size = 0
// 即isEmpty這個屬性,是判斷該類的size屬性是否等於0
val isEmpty : Boolean
get() = this.size == 0
// 另一個例子
val num = 2
get() = if (field > 5) 10 else 0
}
// 測試
fun main(args: Array<String>) {
val mime = Mime()
println("isEmpty = ${mime.isEmpty}")
println("num = ${mime.num}")
}
複製程式碼
輸出結果為:
isEmpty = true
num = 0
複製程式碼
例2:用var
修飾的屬性自定義情況
class Mime{
var str1 = "test"
get() = field // 這句可以省略,kotlin預設實現的方式
set(value){
field = if (value.isNotEmpty()) value else "null"
}
var str2 = ""
get() = "隨意怎麼修改都不會改變"
set(value){
field = if (value.isNotEmpty()) value else "null"
}
}
// 測試
fun main(args: Array<String>) {
val mime = Mime()
println("str = ${mime.str1}")
mime.str1 = ""
println("str = ${mime.str1}")
mime.str1 = "kotlin"
println("str = ${mime.str1}")
println("str = ${mime.str2}")
mime.str2 = ""
println("str = ${mime.str2}")
mime.str2 = "kotlin"
println("str = ${mime.str2}")
}
複製程式碼
輸出結果為:
str = test
str = null
str = kotlin
str = 隨意怎麼修改都不會改變
str = 隨意怎麼修改都不會改變
str = 隨意怎麼修改都不會改變
複製程式碼
經過上面的例項,我總結出了以下幾點:
- 使用了
val
修飾的屬性,不能有setter()
.- 不管是
val
還是var
修飾的屬性,只要存在getter()
,其值再也不會變化- 使用
var
修飾的屬性,可以省略掉getter()
,不然setter()
毫無意義。當然get() = field
除外。而get() = field
是Koltin
預設的實現,是可以省略這句程式碼的。
故而,在實際的專案開發中,這個自定義的getter
與setter
的意義不是太大。
2.2、修改訪問器的可見性
如果您對
Kotlin
中的可見性修飾符還不瞭解的話,請參見Kotlin——中級篇(三):可見性修飾符詳解
修改屬性訪問器在實際的開發中其實也沒有太大的作用,下面舉個例子就明白了:
例:
var str1 = "kotlin_1"
private set // setter()訪問器的私有化,並且它擁有kotlin的預設實現
var test : String?
@Inject set // 用`Inject`註解去實現`setter()`
val str2 = "kotlin_2"
private set // 編譯錯誤,因為val修飾的屬性,不能有setter
var str3 = "kotlin_3"
private get // 編譯出錯,因為不能有getter()的訪問器可見性
fun main(args: Array<String>) {
// 這裡虛擬碼
str1 = "能不能重新賦值呢?" // 編譯出錯,因為上面的setter是私有的
}
複製程式碼
如果,屬性訪問器的可見性修改為private
或者該屬性直接使用private
修飾時,我們只能手動提供一個公有的函式去修改其屬性了。就像Java
中的Bean
的setXXXX()
三、後備欄位
Kotlin的類不能有欄位。 但是,有時在使用自定義訪問器時需要有一個後備欄位。為了這些目的,Kotlin提供了可以使用欄位識別符號訪問的自動備份欄位
例:
var count = 0 // 初始化值會直接寫入備用欄位
set(value){
field = if(value > 10) value else 0 // 通過field來修改屬性的值。
}
複製程式碼
注意:
field
識別符號只能在屬性的訪問器中使用。這在上面提到過- 如果屬性使用至少一個訪問器的預設實現,或者自定義訪問器通過
field
識別符號引用它,則將為屬性生成後備欄位。
看上面的例子,使用了getter()
的預設實現。又用到了field
識別符號。
例:不會生成後備欄位的屬性
val size = 0
/*
沒有後備欄位的原因:
1. 並且`getter()`不是預設的實現。沒有使用到`field`識別符號
2. 使用`val`修飾,故而不存在預設的`setter()`訪問器,也沒有`field`修飾符
*/
val isEmpty :Boolean
get() = this.size == 0
複製程式碼
不管是後備欄位或者下面的後備屬性,都是Kotlin
對於空指標的一種解決方案,可以避免函式訪問私有屬性而破壞它的結構。
這裡值得強調的一點是,setter()
中
四、後備屬性
所謂後備屬性,其實是對後備欄位
的一個變種,它實際上也是隱含試的對屬性值的初始化宣告,避免了空指標。
我們根據一個官網的例子,進行說明:
private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
get() {
if (_table == null) {
_table = HashMap() // 初始化
}
// ?: 操作符,如果_table不為空則返回,反之則丟擲AssertionError異常
return _table ?: throw AssertionError("Set to null by another thread")
}
複製程式碼
從上面的程式碼中我們可以看出:_table
屬性是私有的,我們不能直接的訪問它。故而提供了一個公有的後備屬性(table
)去初始化我們的_table
屬性。
通俗的講,這和在Java中定義Bean屬性的方式一樣。因為訪問私有的屬性的getter和setter函式,會被編譯器優化成直接反問其實際欄位。因此不會引入函式呼叫開銷。
五、編譯時常數
在開發中,難免會遇到一些已經確定的值的量。在Java
中,我們可以稱其為常量
。在kotlin
中,我們稱其為編譯時常數
。我們可以用const
關鍵字去宣告它。其通常和val
修飾符連用
- 關鍵字:
const
- 滿足
const
的三個條件:
- 物件的頂層或成員,即頂層宣告。
- 初始化為
String
型別或基本型別的值- 沒有定製的
getter()
例:
const val CONST_NUM = 5
const val CONST_STR = "Kotlin"
複製程式碼
關於編譯時常數
這個知識點更詳細的用法,我在講解講解變數的定義這一章節時,已經詳細講解過了。這裡不做累贅。
您可以參見我的Kotlin——初級篇(二):變數、常量、註釋的使用這一篇文章
六、後期初始化屬性
通常,宣告為非空型別的屬性必須在建構函式中進行初始化。然而,這通常不方便。例如,可以通過依賴注入或單元測試的設定方法初始化屬性。 在這種情況下,不能在建構函式中提供非空的初始值設定,但是仍然希望在引用類的正文中的屬性時避免空檢查。故而,後期初始化屬性就應運而生了。
- 關鍵字 :
lateinit
- 該關鍵字必須宣告在類的主體內,並且只能是用
var
修飾的屬性。並且該屬性沒有自定義的setter()
與getter()
函式。- 該屬性必須是非空的值,並且該屬性的型別不能為基本型別。
例:
class Test{
// 宣告一個User物件的屬性
lateinit var user : User
/*
下面的程式碼是錯誤的。因為用lateinit修飾的屬性,不能為基本型別。
這裡的基本型別指 Int、Float、Double、Short等,String型別是可以的
*/
// lateinit var num : Int
}
複製程式碼
關於後期初始化屬性
這一個知識點,我在講解講解變數的定義這一章節時,已經詳細講解過了。這裡不做累贅。不過關於這一知識點,一般都是在Android
開發中或者在依賴注入時會用到。
您可以參見我的Kotlin——初級篇(二):變數、常量、註釋的使用這一篇文章
七、委託屬性
要想把委託屬性
這一個知識點詳細的講解明白。以及它的實現與例項都要花上很大的篇幅去講解。我會單獨抽出一篇文章去講解它,故而這裡就不做累述了。您可以參見文章Kotlin——高階篇(八):委託與委託屬性詳解
總結
在一些文章或者書籍裡面,關於Kotlin
屬性/欄位的講解,不會有這麼多的知識點,並且我們在實際開發中,也有一些知識點很少去用到。這裡大家主要去理解與實踐屬性的基本使用與定義、Getter()
與Setter()
、後期初始化屬性、以及編譯時常數這幾個點就可以了。
如果各位大佬看了之後感覺還闊以,就請各位大佬隨便star
一下,您的關注是我最大的動力。
我的個人部落格:Jetictors
我的github:Jetictors