Kotlin的類型可以有屬性。 這些可以聲明為可變的,使用var關(guān)鍵字或使用val關(guān)鍵字只讀。
class Address {
var name: String = ...
var street: String = ...
var city: String = ...
var state: String? = ...
var zip: String = ...
}
要使用一個(gè)屬性,簡(jiǎn)單地通過(guò)名稱引用它,就好像它是Java中的一個(gè)字段:
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
}
聲明屬性的完整語(yǔ)法是 -
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
只讀屬性聲明的完整語(yǔ)法與可變的屬性聲明的不同之處,有兩種方式:它以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
可以在一個(gè)屬性聲明中寫出自定義訪問(wèn)器,非常像普通功能。 以下是一個(gè)定制getter的例子:
val isEmpty: Boolean
get() = this.size == 0
自定義設(shè)置器(setter)如下所示:
var stringRepresentation: String
get() = this.toString()
set(value) {
setDataFromString(value) // parses the string and assigns values to other properties
}
按照慣例,setter參數(shù)的名稱是value,可以選擇或使用不同的名稱。
從Kotlin 1.1起,如果可以從getter推斷屬性類型,則可以省略它:
val isEmpty get() = this.size == 0 // has type Boolean
如果需要更改訪問(wèn)器的可見(jiàn)性或注釋它,但不需要更改默認(rèn)實(shí)現(xiàn),可以定義訪問(wèn)器而不定義其主體:
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的類不能有字段。 但是,有時(shí)在使用自定義訪問(wèn)器時(shí)需要有一個(gè)后備字段。 為了這些目的,Kotlin提供了可以使用字段標(biāo)識(shí)符訪問(wèn)的自動(dòng)備份字段:
var counter = 0 // the initializer value is written directly to the backing field
set(value) {
if (value >= 0) field = value
}
field標(biāo)識(shí)符只能在屬性的訪問(wèn)器中使用。
如果屬性使用至少一個(gè)訪問(wèn)器的默認(rèn)實(shí)現(xiàn),或者自定義訪問(wèn)器通過(guò)field標(biāo)識(shí)符引用它,則將為屬性生成后備字段。
例如,在以下情況下,將不會(huì)有后備字段:
val isEmpty: Boolean
get() = this.size == 0
如果想做一些不符合這個(gè)“隱性后備字段”方案的東西,總是可以回到擁有一個(gè)后備屬性:
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中的一樣,因?yàn)槭褂媚J(rèn)getter和setter的私有屬性的訪問(wèn)被優(yōu)化,因此不會(huì)引入函數(shù)調(diào)用開銷。
在編譯時(shí)已知其值的屬性可以使用const修飾符標(biāo)記為編譯時(shí)常數(shù)。 這些屬性需要滿足以下要求:
getter這些屬性可以在注釋中使用:
const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"
@Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { ... }
通常,聲明為非空類型的屬性必須在構(gòu)造函數(shù)中進(jìn)行初始化。 然而,這通常不方便。 例如,可以通過(guò)依賴注入或單元測(cè)試的設(shè)置方法初始化屬性。 在這種情況下,不能在構(gòu)造函數(shù)中提供非空的初始值設(shè)置,但是仍然希望在引用類的正文中的屬性時(shí)避免空檢查。
要處理這種情況,可以使用lateinit修飾符標(biāo)記屬性:
public class MyTest {
lateinit var subject: TestSubject
@SetUp fun setup() {
subject = TestSubject()
}
@Test fun test() {
subject.method() // dereference directly
}
}
修飾符只能用于在一個(gè)類的主體內(nèi)聲明的var屬性(不在主構(gòu)造函數(shù)中),并且只有當(dāng)該屬性沒(méi)有自定義的getter或setter時(shí)才可以使用。 屬性的類型必須為非空值,并且不能為原始類型。
在初始化之前訪問(wèn)一個(gè)lateinit屬性會(huì)引發(fā)一個(gè)特殊的異常,清楚地標(biāo)識(shí)被訪問(wèn)的屬性以及它還沒(méi)被初始化的事實(shí)。
最常見(jiàn)的屬性只是讀取(也可能寫入)支持字段。 另一方面,使用定制getter和setter可以實(shí)現(xiàn)屬性的任何行為。屬性如何運(yùn)作有一些共同的模式。 幾個(gè)例子:懶值,通過(guò)給定的鍵讀取映射,訪問(wèn)數(shù)據(jù)庫(kù),通知訪問(wèn)者等。