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>]
初始化程序,getter
和setter
是可選的。 如果可以從初始化程序(或從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中的一樣,因爲使用默認getter
和setter
的私有屬性的訪問被優化,因此不會引入函數調用開銷。
編譯時常數
在編譯時已知其值的屬性可以使用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
屬性(不在主構造函數中),並且只有當該屬性沒有自定義的getter
或setter
時纔可以使用。 屬性的類型必須爲非空值,並且不能爲原始類型。
在初始化之前訪問一個lateinit
屬性會引發一個特殊的異常,清楚地標識被訪問的屬性以及它還沒被初始化的事實。
委託屬性
最常見的屬性只是讀取(也可能寫入)支持字段。 另一方面,使用定製getter
和setter
可以實現屬性的任何行爲。屬性如何運作有一些共同的模式。 幾個例子:懶值,通過給定的鍵讀取映射,訪問數據庫,通知訪問者等。