Kotlin——中級篇(二): 屬性與字段詳解


在前面的章節中,詳細的為大家講解到了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}這樣的一句代碼。其中valueKoltinsetter()函數時其參數的約定俗成的習慣。你也可以換成其他的值。而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 = 隨意怎么修改都不會改變

經過上面的實例,我總結出了以下幾點:

  1. 使用了val修飾的屬性,不能有setter().
  2. 不管是val還是var修飾的屬性,只要存在getter(),其值再也不會變化
  3. 使用var修飾的屬性,可以省略掉getter(),不然setter()毫無意義。當然get() = field除外。而get() = fieldKoltin默認的實現,是可以省略這句代碼的。

故而,在實際的項目開發中,這個自定義的gettersetter的意義不是太大。

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中的BeansetXXXX()

三、后備字段

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的三個條件:
    1. 對象的頂層或成員,即頂層聲明。
    2. 初始化為String類型或基本類型的值
    3. 沒有定制的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
GithubJteictors
掘金Jteictors

歡迎各位大佬進群共同研究、探索

QQ群號:497071402


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM