Kotlin學習之-5.2 屬性和成員
定義屬性
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
}
Getter 和Setter
定義屬性完整語法結構如下:
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
構造器、getter 和setter都是可選項。如果屬性的類型可以從構造器中推斷出來的話,那麼屬性的類型也是可選的。示例如下:
var allByDefault: Int? // error: 需要顯式的構造器。 有默認的getter 和setter
var initialized = 1 // 類型是Int。 有默認的getter 和setter
只讀屬性的語法和變量屬性的的語法有兩點不同: 定義是用val
而不是var
,並且沒有setter 方法
val simple: Int? // 類型是Int,默認的getter方法,必須在構造函數中初始化。
val inferredType = 1 // 類型是Int, 默認的getter方法。
我們可以在屬性定義時自定義getter方法,和普通的函數非常像。示例如下:
val isEmpty: Boolean
get() = this.size == 0
自定義setter方法示例如下:
var stringRepresentation: String
get() = this.toString()
set(value) {
setDataFromString(value) // 解析字符串並且賦值給其他屬性
}
一般情況,setter方法的參數名是value
,但是也可以選擇一個其他名字。
從Kotlin v1.1開始,如果屬性類型可以從getter方法中推斷出來,那麼定義的時候可以省略屬性類型。
val isEmpty() get() = this.size == 0 // 推斷的類型Boolean
如果要更改函數的可見性或者添加註解,但是不需要乾煸默認的實現,可以定義訪問函數但不定義函數主體。
var setterVisibility: String = "abc"
private set
var setterWithAnnotation: Any? = null
@inject set
Backing Fields
Kotlin中,類沒有成員。但是有時候使用自定義訪問函數時也需要一個’backing field’。 在這些用法中,Kotlin提供一個自動的’backing field’, 它可以用field
關鍵字來訪問。
var counter = 0
set(value) {
if (value >= 0) field = value
}
field
描述符只能用在屬性的訪問。
屬性會產生一個’backing field’,如果屬性有至少一個默認實現的訪問函數,或者自定義的訪問函數使用了field
描述符來訪問這個屬性。
例如,下面的例子中沒有沒有’backing field’
val isEmpty: Boolean
get() = this.size == 0
Backing Properties
如果你需要做的事情不符合隱式backing field模式,那麼可以使用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
的成員 - 是用
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。屬性的類型必須是非空的,並且不能是基礎類型。
在屬性初始化之前,訪問一個延遲初始化lateinit
的屬性,會拋出一個特殊的異常,該異常表明屬性在訪問的時候,還沒有初始化。
代理屬性
最常用的屬性就是簡單地讀取或者寫入backing field。 另外,使用自定義的getter和setter可以實現屬性任意的操作。 除了這兩種情況,還有一些常見的用法。例如,延遲初始化的值,用一個給定的關鍵字從一個map中獲取對應的值,訪問數據庫,通知監聽器等等。
這些常用的行爲和操作可以通過庫的方式來實現。這可以使用代理屬性的方式來實現。
PS,我會堅持把這個系列寫完,有問題可以留言交流,也關注專欄Kotlin for Android Kotlin安卓開發