1. 程式人生 > >Kotlin學習之類與物件篇—屬性與欄位

Kotlin學習之類與物件篇—屬性與欄位

屬性的宣告

Kotlin類中可以有屬性,屬性可以被宣告為可變屬性,用var關鍵字修飾;或者宣告為只讀屬性,用val修飾。

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

要使用一個屬性,可以直接通過它的名字呼叫它:

fun copyAddress(address: Address): Address {
    val result = Address() 
    result.name = address.name 
    result.street = address.street
    // ...
return result }

Getters 和 Setters

宣告一個屬性的完整語法如下:

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

其中initializer, gettersetter都是可選項。如果屬性的型別可推斷(或者可以從getter的返回值型別推斷),propertyType也是可選項。

例子:

var allByDefault: Int? // 報錯: 屬性未被初始化
var initialized = 1 // 屬性 type 為 Int, 生成預設 getter 和 setter

只讀屬性的宣告和可變屬性有些不一樣,它沒有setter,畢竟只讀。

我們能自定義存取器,下面是一個自定義的getter:

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

一個自定義的setter如下:

var stringRepresentation: String
    get() = this.toString()
    set(value) {
        setDataFromString(value
) }

方便起見,setter引數的名字是value,但這並不是關鍵字,你也可以給引數起其它名字。

如果需要改變存取器的可見性或者註釋它,可以只定義存取器而不定義其實現,如下:

var setterVisibility: String = "abc"
    private set // private 的 setter,擁有預設實現

var setterWithAnnotation: Any? = null
    @Inject set // 被Inject註釋的setter
(1). Backing Fields

這個不好翻譯,看到有人譯作幕後欄位,也有人譯作影子欄位,它的作用是在存取器中代指屬性本身。

Kotlin中不支援直接宣告欄位。然而,當一個屬性需要backing field的時候,Kotlin會自動為它提供。backing field在存取器中用識別符號field引用。

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

注意:field識別符號只能在屬性的存取器中被使用。

當一個屬性預設實現至少一個存取器方法(getter/setter)或者在自定義存取器中使用field關鍵字引用的時候,它就會生成一個backing field。下面這個例子就沒有生成backing field:

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

如果backing field無法滿足需求,那麼可以使用backing properties:

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方法,自從Kotlin 1.2,也可用於頂層的屬性和區域性變數。屬性和變數必須為非空,而且不能是原始資料型別。

訪問一個還沒被初始化的lateinit屬性將會丟擲錯誤。

檢查一個 lateinit var 是否已被初始化(自從kotlin 1.2)

要檢查一個lateinit var 是否已被初始化,可以在該屬性的引用上使用.isInitialized方法。

if (foo::bar.isInitialized) {
    println(foo.bar)
}

此檢測僅對可詞法級訪問的屬性可用,即宣告位於同一個型別內、位於其中一個外圍型別中或者位於相同檔案的頂層的屬性。

上面這段話不是特別明白 … …