Kotlin學習之-5.2 屬性和成員

Kotlin學習之-5.2 屬性和成員

定義屬性

Kotlin中,類可以有屬性。它們既可以用var關鍵字定義成變量,也可以用val關鍵字定義成只讀量。

class Address {
    var name: String = ...
    var street: String = ...
    var city: String = ...
    var state: String? = ...
    var zip: String = ...
}

使用一個屬性,只要簡單的用它的名字引用即可,就像Java中的成員一樣。

fun copyAddress(address: Address): Address {
    val result = Address() // Kotlin中沒有'new' 關鍵字
    result.name = address.name
    result.street = address.street
    //...
    return result
}

Getter 和Setter

定義屬性完整語法結構如下:

var <propertyName>[: <PropertyType>] [= <property_initializer>]
    [<getter>]
    [<setter>]

構造器、getter 和setter都是可選項。如果屬性的類型可以從構造器中推斷出來的話,那麼屬性的類型也是可選的。示例如下:

var allByDefault: Int? // error: 需要顯式的構造器。 有默認的getter 和setter
var initialized = 1 // 類型是Int。   有默認的getter 和setter

只讀屬性的語法和變量屬性的的語法有兩點不同: 定義是用val 而不是var,並且沒有setter 方法

val simple: Int? // 類型是Int,默認的getter方法,必須在構造函數中初始化。
val inferredType = 1 // 類型是Int, 默認的getter方法。

我們可以在屬性定義時自定義getter方法,和普通的函數非常像。示例如下:

val isEmpty: Boolean
    get() = this.size == 0

自定義setter方法示例如下:

var stringRepresentation: String
    get() = this.toString()
    set(value) {
        setDataFromString(value) // 解析字符串並且賦值給其他屬性
    }

一般情況,setter方法的參數名是value,但是也可以選擇一個其他名字。
從Kotlin v1.1開始,如果屬性類型可以從getter方法中推斷出來,那麼定義的時候可以省略屬性類型。

val isEmpty() get() = this.size == 0 // 推斷的類型Boolean

如果要更改函數的可見性或者添加註解,但是不需要乾煸默認的實現,可以定義訪問函數但不定義函數主體。

var setterVisibility: String = "abc"
    private set

var setterWithAnnotation: Any? = null
    @inject set

Backing Fields

Kotlin中,類沒有成員。但是有時候使用自定義訪問函數時也需要一個’backing field’。 在這些用法中,Kotlin提供一個自動的’backing field’, 它可以用field關鍵字來訪問。

var counter = 0
    set(value) {
        if (value >= 0) field = value
    }

field描述符只能用在屬性的訪問。

屬性會產生一個’backing field’,如果屬性有至少一個默認實現的訪問函數,或者自定義的訪問函數使用了field描述符來訪問這個屬性。
例如,下面的例子中沒有沒有’backing field’

val isEmpty: Boolean
    get() = this.size == 0

Backing Properties

如果你需要做的事情不符合隱式backing field模式,那麼可以使用backing property

private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
    get() {
        if (_table == null) {
            _table = HashMap() // 類型參數是推斷出來的
        }
        return _table ?: throw AssertionError("Set to null by another thread")
    }

總的來說,這和Java是一樣的,使用默認的getter和setter方法訪問私有的屬性會被優化,從而沒有函數調用的負擔。

編譯期常量

屬性的值在編譯期就確定的話,用const修飾符來表示成編譯期常量。這樣的屬性需要滿足如下要求。

  • 頂級的或者是一個object的成員
  • 是用String類型或者基礎類型初始化的
  • 沒有自定義getter方法

這樣的屬性可以在註解中使用

const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"

@Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { ... }

延遲初始化的屬性

一般情況,被定義成非空的屬性必須在構造函數中初始化。但是,這樣通常不太方便。例如,屬性可以在依賴注入的時候被初始化,或者在單元測試的setup方法中初始化。 在這種情況,就不能再構造函數中提供一個非空的初始化器,但是你仍然想要在引用屬性的時候避免空指針檢查。

處理這種情況,需要用lateinit 描述符來標識這個屬性。

public class MyTest {
    lateinit var subject: TestSubject

    @SetUp fun setup() {
        subject = TestSubject()
    }

    @Test fun test() {
        subject.method()
    }
}

延遲初始化lateinit 描述符只能用在var屬性,並且屬性不能有自定義的getter和setter。屬性的類型必須是非空的,並且不能是基礎類型。

在屬性初始化之前,訪問一個延遲初始化lateinit的屬性,會拋出一個特殊的異常,該異常表明屬性在訪問的時候,還沒有初始化。

代理屬性

最常用的屬性就是簡單地讀取或者寫入backing field。 另外,使用自定義的getter和setter可以實現屬性任意的操作。 除了這兩種情況,還有一些常見的用法。例如,延遲初始化的值,用一個給定的關鍵字從一個map中獲取對應的值,訪問數據庫,通知監聽器等等。

這些常用的行爲和操作可以通過庫的方式來實現。這可以使用代理屬性的方式來實現。


PS,我會堅持把這個系列寫完,有問題可以留言交流,也關注專欄Kotlin for Android Kotlin安卓開發

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章