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() // there's no 'new' keyword in Kotlin
    result.name = address.name // accessors are called
    result.street = address.street
    // ...
    return result
}

Getters 和 Setters

聲明屬性的完整語法是 -

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

初始化程序,gettersetter是可選的。 如果可以從初始化程序(或從getter返回類型,如下所示)推斷屬性類型,則屬性類型是可選的。

例子:

var allByDefault: Int? // error: explicit initializer required, default getter and setter implied
var initialized = 1 // has type Int, default getter and setter

只讀屬性聲明的完整語法與可變的屬性聲明的不同之處,有兩種方式:它以val而不是var開頭,不允許setter

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

可以在一個屬性聲明中寫出自定義訪問器,非常像普通功能。 以下是一個定製getter的例子:

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

自定義設置器(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推斷屬性類型,則可以省略它:

val isEmpty get() = this.size == 0  // has type Boolean

如果需要更改訪問器的可見性或註釋它,但不需要更改默認實現,可以定義訪問器而不定義其主體:

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提供了可以使用字段標識符訪問的自動備份字段:

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

field標識符只能在屬性的訪問器中使用。
如果屬性使用至少一個訪問器的默認實現,或者自定義訪問器通過field標識符引用它,則將爲屬性生成後備字段。

例如,在以下情況下,將不會有後備字段:

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

後備屬性

如果想做一些不符合這個「隱性後備字段」方案的東西,總是可以回到擁有一個後備屬性:

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中的一樣,因爲使用默認gettersetter的私有屬性的訪問被優化,因此不會引入函數調用開銷。

編譯時常數

在編譯時已知其值的屬性可以使用const修飾符標記爲編譯時常數。 這些屬性需要滿足以下要求:

  • 對象的頂層或成員
  • 初始化爲String類型或原始類型的值
  • 沒有定製的getter

這些屬性可以在註釋中使用:

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

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

後期初始化屬性

通常,聲明爲非空類型的屬性必須在構造函數中進行初始化。 然而,這通常不方便。 例如,可以通過依賴注入或單元測試的設置方法初始化屬性。 在這種情況下,不能在構造函數中提供非空的初始值設置,但是仍然希望在引用類的正文中的屬性時避免空檢查。

要處理這種情況,可以使用lateinit修飾符標記屬性:

public class MyTest {
    lateinit var subject: TestSubject

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

    @Test fun test() {
        subject.method()  // dereference directly
    }
}

修飾符只能用於在一個類的主體內聲明的var屬性(不在主構造函數中),並且只有當該屬性沒有自定義的gettersetter時纔可以使用。 屬性的類型必須爲非空值,並且不能爲原始類型。

在初始化之前訪問一個lateinit屬性會引發一個特殊的異常,清楚地標識被訪問的屬性以及它還沒被初始化的事實。

委託屬性

最常見的屬性只是讀取(也可能寫入)支持字段。 另一方面,使用定製gettersetter可以實現屬性的任何行爲。屬性如何運作有一些共同的模式。 幾個例子:懶值,通過給定的鍵讀取映射,訪問數據庫,通知訪問者等。