天天看點

Kotlin 學習之屬性和字段屬性和字段

屬性和字段

屬性聲明

在 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 // accessors are called
    result.street = address.street
    // ...
    return result
}
           

Getters 和 Setters

聲明一個屬性的完整文法如下:

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

文法中的初始化語句,

getter

setter

都是可選的。如果屬性類型可以從初始化語句或者

getter

的傳回類型中推斷出來,那麼他的類型也是忽略的。

例子:

var allByDefault: Int? // 錯誤: 需要一個初始化語句, 預設實作了 getter和 setter 方法
var initialized =  類型為 Int, 預設實作了 getter 和 setter
           

隻讀屬性的聲明文法和可變屬性的聲明文法相比有兩點不同: 它以

val

而不是

var

開頭,不允許

setter

函數:

val simple: Int? // has type Int, default getter, must be initialized in constructor
val inferredType =  // has type Int and a default getter
           

我們可以像寫普通函數那樣在屬性聲明中自定義的通路器,下面是一個自定義的

getter

的例子:

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

下面是一個自定義的setter:

var stringRepresentation: String
    get() = this.toString()
    set(value) {
        setDataFromString(value) // parses the string and assigns values to other properties
    }
           

為了友善起見,

setter

方法的參數名是

value

,你也可以自己任選一個自己喜歡的名稱。

從Kotlin 1.1開始,如果可以從getter方法推斷出類型則可以省略之:

如果你需要改變一個通路器的可見性或者給它添加注解,但又不想改變預設的實作,那麼你可以定義一個不帶函數體的通路器:

var setterVisibility: String = "abc"
    private set // the setter is private and has the default implementation

var setterWithAnnotation: Any? = null
    @Inject set // annotate the setter with Inject
           

備用字段

在 kotlin 中類不可以有字段。然而當使用自定義的通路器時有時候需要備用字段。出于這些原因 kotlin 使用

field

關鍵詞提供了自動備用字段:

var counter =  // the initializer value is written directly to the backing field
    set(value) {
        if (value >= ) field = value
    }
           

field 關鍵詞隻能用于屬性的通路器,編譯器會檢查通路器的代碼, 如果使用了備用字段(或者通路器是預設的實作邏輯),就會自動生成備用字段,否則就不會。

比如下面的例子中就不會有備用字段:

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

初次看到備用字段的時候上面的代碼你可能會這麼想:

var counter = 
        set(value) {
            if (value >= ) this.counter = value
        }
           

this.counter

代替

field

是不是等效的

Kotlin 學習之屬性和字段屬性和字段

但是結果卻是發生了死循環。

分析

this.counter = value

這樣的指派語句在kotlin中實際就是調用了屬性的

set(value)

方法,這樣其實就是形成了無休止的遞歸。

備用屬性

如果你想要做一些事情但不适合這種 “隐含備用字段” 方案,你可以試着用備用屬性的方式:

private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
    get() {
        if (_table == null) {
            _table = HashMap() // Type parameters are inferred
        }
        return _table ?: throw AssertionError("Set to null by another thread")
    }
           

綜合來講,這些和 java 很相似,可以避免函數通路私有屬性而破壞它的結構。

編譯時常量

那些在編譯時就能知道具體值的屬性可以使用 const 修飾符标記為 編譯時常量。這種屬性需要同時滿足以下條件:

  • 在top-level聲明的 或者 是一個 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()  // dereference directly
    }
}
           

這個修飾符隻能夠被用在類的

var

類型的可變屬性定義中,不能用在構造方法中。

并且屬性不能有自定義的

getter

setter

通路器。

這個屬性的類型必須是非空的,同樣也不能為一個基本類型。

在一個

lateinit

的屬性初始化前通路他,會導緻一個特定異常,告訴你通路的時候值還沒有初始化.

複寫屬性

參看 複寫屬性

代理屬性

最常見的屬性就是從備用屬性中讀(或者寫)。另一方面,自定義的

getter

setter

可以實作屬性的任何操作。有些像懶值( lazy values ),根據給定的關鍵字從map 中讀出,讀取資料庫,通知一個監聽者等等,像這些介于

getter

setter

模式之間的操作可以通過代理屬性作為庫來實作。更多請參看代理屬性.