屬性和字段
屬性聲明
在 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
是不是等效的
但是結果卻是發生了死循環。
分析
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
模式之間的操作可以通過代理屬性作為庫來實作。更多請參看代理屬性.