理解 Kotlin 中的屬性(property)

Howshea發表於2019-04-12

這篇文章是一時興起想寫的,因為我發現我對Kotlin的屬性理解一直有誤

Java 中的屬性是什麼(property)

首先我們要搞清楚在 Java 中屬性是什麼,在 Java 中類的屬性不是指一個欄位,而是一個欄位和它的get、set方法加在一起才算一個屬性,比如:

class Person {
    int age;
    public int getAge(){
        return age;
    }
    public void setAge(int age){
        this.age = age
    }
}
複製程式碼

而如果不給這個 name 寫get、set方法,它就只是一個 field,可以稱它為 欄位 或者

Kotlin的類只有屬性(property)沒有獨立的欄位(field)

如果上面的程式碼用kotlin翻譯一下:

class Person {
    var age = 0
}
複製程式碼

可以對比出,Kotlin裡不需要額外的get、set方法,當然你也寫不了,因為 Kotlin 已經預設實現了get、set,所以在Kotlin裡,我們寫不出 field

Kotlin 中的 backing field 是什麼

前面說 Kotlin 中不能寫 field,但是我們很多時候必須要用到 field,比如你複寫一個屬性的set方法

var age = 0
    set(value){
        age = value + 1
    }
複製程式碼

如果這樣寫其實是會發生遞迴,無法賦值成功

理解 Kotlin 中的屬性(property)
這裡AS也提醒你了,這裡發生了遞迴
所以我們一般都這麼寫:

var age = 0
    set(value){
        field = value + 1
    }
複製程式碼

這裡出現了一個 field 的東西可以訪問和賦值,這個東西就是 Kotlin 的幕後欄位(Backing Field)
我們可以簡單地理解為,Kotlin 沒有明面上的 field,但是它存在於幕後

沒有 backing field的成員是什麼?

class Person{
    private var _age =0
    var age:Int
        get() = _age
        set(value) {
            _age = value
        }
}
複製程式碼

當我們宣告一個 var 為私有時,比如上面的_age,我們叫它 幕後屬性 ,雖然它看起來不需要get、set方法,但是其實仍然是有的,只不過它的get、set方法都被宣告為 private 了

當然我們這裡不是想討論幕後屬性,而是要討論一下這個 age 是個什麼玩意,是一個成員屬性嗎,其實通過位元組碼就可以知道,這裡的Person類實際並不存在age這個成員,它只是幫你生成了對_age的兩個public final的get和set方法

// access flags 0x2
private I _age

// access flags 0x11
public final getAge()I
 L0
  LINENUMBER 28 L0
  ALOAD 0
複製程式碼

和你自己直接寫一個

fun getAge() = _age
複製程式碼

是一模一樣的

再舉一個例子

var View.topPadding: Int
    inline get() = paddingTop
    set(value) = setPadding(paddingLeft, value, paddingRight, paddingBottom)
複製程式碼

這是從anko的拷出來一段程式碼,通過這個擴充套件成員,我們可以直接對某個 View 的 PaddingTop 進行修改和讀取,雖然說是成員,但是我們把一段位元組碼拿出來看一下:

// access flags 0x19
public final static getTopPadding(Landroid/view/View;)I
  @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0****
...
// access flags 0x19
public final static setTopPadding(Landroid/view/View;I)V
  @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
複製程式碼

很明顯,這裡就是直接生成了兩個靜態函式getTopPaddingsetTopPadding,並不是真的為View這個類新增了一個成員,那這個東西到底什麼呢?我們把上面的程式碼稍微改一下:

var View.topPadding: Int = 0 
    inline get() = paddingTop
    set(value) = setPadding(paddingLeft, value, paddingRight, paddingBottom)
複製程式碼

給這個成員加個預設值,可以看到,編輯器報錯了

理解 Kotlin 中的屬性(property)
並且告訴你這個屬性沒有幕後欄位,所以不能初始化,好吧,官方給出了定義,這就是一個屬性(property)

總結

在Kotlin中一個 property 不管有沒有 backing field 都稱之為 property,而在 Java 中 field + get、set方法一起才能是一個 property。
如果我們從Java 的角度去看一個沒有 backing field 的 property,可以理解為 Kotlin 對 以get、set開頭這樣的函式的語法糖,這種語法糖有什麼用呢?個人覺得是為了DSL語法服務的,還是以上面那個topPadding為例,當你在用 DSL 語法設定一個view的時候,比如:

view.apply {
    background = getDrawable(R.drawable.bg)
    visibility = View.INVISIBLE
    ...
}
複製程式碼

前面都是一個屬性等於一個值,這個時候下面跟上 topPadding = xxx,語義十分清晰連貫,如果這裡突然用一個 setTopPadding(this,xxx) ,不僅程式碼不美觀,而且打斷了閱讀程式碼和編寫程式碼的人的思維上的連貫性。

以上就是我對 Kotlin 的 Property 的理解

相關文章