在前面的章節中,詳細的為大家講解到了Kotlin
中對類的類的定義、使用、初始化
、初始化、類繼承
等內容,但是在一個類中,幾乎上是不可能不出現屬性與字段(field)
的,這一篇文章就為大家奉上Kotlin
中屬性與字段
的定義、使用及高級操作等。如果您目前對Kotlin
中的類沒有一個認知的話,請參見Kotlin——中級篇(一):類(class)詳解.
目錄
一、屬性的基礎使用
一個類中是可以存在屬性的,一個屬性可以用
val
或者var
修飾。
- 用
val
修飾符修飾的屬性是只讀的,即不能被修改,只可使用- 用
var
修飾符修飾的屬性是可讀寫的,即能用能改
例:
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:Jteictors
掘金:Jteictors