屬性與字段
聲明屬性
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
}
Getters 與 Setters
聲明一個屬性的完整語法是
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
其初始器(initializer)、getter 和 setter 都是可選的。屬性類型如果可以從初始器 (或者從其 getter 返回值,如下文所示)中推斷出來,也可以省略。
例如:
var allByDefault: Int? // 錯誤:需要顯式初始化器,隱含默認 getter 和 setter
var initialized = 1 // 類型 Int、默認 getter 和 setter
一個只讀屬性的語法和一個可變的屬性的語法有兩方面的不同:1、只讀屬性的用 val
開始代替var
2、只讀屬性不允許 setter
val simple: Int? // 類型 Int、默認 getter、必須在構造函數中初始化
val inferredType = 1 // 類型 Int 、默認 getter
我們可以爲屬性定義自定義的訪問器。如果我們定義了一個自定義的 getter,那麼每次訪問該屬性時都會調用它 (這讓我們可以實現計算出的屬性)。以下是一個自定義 getter 的示例:
val isEmpty: Boolean
get() = this.size == 0
如果我們定義了一個自定義的 setter,那麼每次給屬性賦值時都會調用它。一個自定義的 setter 如下所示:
var stringRepresentation: String
get() = this.toString()
set(value) {
setDataFromString(value) // 解析字符串並賦值給其他屬性
}
按照慣例,setter 參數的名稱是 value
,但是如果你喜歡你可以選擇一個不同的名稱。
自 Kotlin 1.1 起,如果可以從 getter 推斷出屬性類型,則可以省略它:
val isEmpty get() = this.size == 0 // 具有類型 Boolean
如果你需要改變一個訪問器的可見性或者對其註解,但是不需要改變默認的實現, 你可以定義訪問器而不定義其實現:
var setterVisibility: String = "abc"
private set // 此 setter 是私有的並且有默認實現
var setterWithAnnotation: Any? = null
@Inject set // 用 Inject 註解此 setter
幕後字段
在 Kotlin 類中不能直接聲明字段。然而,當一個屬性需要一個幕後字段時,Kotlin 會自動提供。這個幕後字段可以使用field
標識符在訪問器中引用:
var counter = 0 // 注意:這個初始器直接爲幕後字段賦值
set(value) {
if (value >= 0) field = value
}
field
標識符只能用在屬性的訪問器內。
如果屬性至少一個訪問器使用默認實現,或者自定義訪問器通過 field
引用幕後字段,將會爲該屬性生成一個幕後字段。
例如,下面的情況下, 就沒有幕後字段:
val isEmpty: Boolean
get() = this.size == 0
幕後屬性
如果你的需求不符合這套“隱式的幕後字段”方案,那麼總可以使用 幕後屬性(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 聲明 或 companion 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() // 直接解引用
}
}
該修飾符只能用於在類體中的屬性(不是在主構造函數中聲明的 var
屬性,並且僅當該屬性沒有自定義 getter 或 setter 時),而自 Kotlin 1.2 起,也用於頂層屬性與局部變量。該屬性或變量必須爲非空類型,並且不能是原生類型。
在初始化前訪問一個 lateinit
屬性會拋出一個特定異常,該異常明確標識該屬性被訪問及它沒有初始化的事實。
檢測一個 lateinit var 是否已初始化(自 1.2 起)
要檢測一個 lateinit var
是否已經初始化過,請在該屬性的引用上使用 .isInitialized
:
if (foo::bar.isInitialized) {
println(foo.bar)
}
此檢測僅對可詞法級訪問的屬性可用,即聲明位於同一個類型內、位於其中一個外圍類型中或者位於相同文件的頂層的屬性。
覆蓋屬性
參見覆蓋屬性
委託屬性
最常見的一類屬性就是簡單地從幕後字段中讀取(以及可能的寫入)。 另一方面,使用自定義 getter 和 setter 可以實現屬性的任何行爲。 介於兩者之間,屬性如何工作有一些常見的模式。一些例子:惰性值、 通過鍵值從映射讀取、訪問數據庫、訪問時通知偵聽器等等。
這些常見行爲可以通過使用委託屬性實現爲庫。