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
, getter
和 setter
都是可選項。如果屬性的型別可推斷(或者可以從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)
}
此檢測僅對可詞法級訪問的屬性可用,即宣告位於同一個型別內、位於其中一個外圍型別中或者位於相同檔案的頂層的屬性。
上面這段話不是特別明白 … …