在 Kotlin 裡,你可以宣告一個類別,裡面可以放入 mutable(可變的) 跟 immutable(不可變的) 的成員變數,分別以 var 以及 val 代表,var 代表著可變的,也就是說如果你宣告完一個值給這個變數以後,之後仍然可以改變這個變數的值,val 則是相反,一但確定了值,就不能再進行變動了。
說明
假設你宣告一個類別,就可以如下面範例所示。
class Address {
var name: String = "Holmes, Sherlock"
var street: String = "Baker"
var city: String = "London"
var state: String? = null
var zip: String = "123456"
}
如果你要使用屬性,可以透過物件,在 Kotlin 宣告一個物件,跟 Java 不太一樣的是不需要加上 new 這個關鍵字,然而如果要使用屬性,就直接透過物件取得即可。
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
}
在 Kotlin 裡面,一個屬性內建完整的宣告如下。
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
可以看到分成幾個部分,其中 property_initializer 取決於 PropertyType 是否要寫,如果有初始化 property_initializer 那麼就不用宣告 PropertyType,反之則可以宣告也可以不宣告,getter 跟 setter 則是可以自由選擇要不要設定。
// error: explicit initializer required, default getter and setter implied
var name: String
// has type Int, default getter and setter
var initialized = 1
這邊可以看到如果你有設定 PropertyType 那麼就要給定一個初始值,否則會發生錯誤,如果你想要後面再設定則可以使用 lateinit 來處理,如果你連 PropertyType 都沒有設定,只需要給定相對應的值,則會自動幫你做型態的判定。
val simple: Int? // has type Int, default getter, must be initialized in constructor
val inferredType = 1 // has type Int and a default getter
如果你是透過 val
的方式宣告變數,那麼預設只有 getter 可以使用。
val isEmpty: Boolean
get() = this.size == 0
如果你是透過 var
的方式宣告變數,那麼就可以客製化自己的 getter / setter,其中你可以使用你喜歡的參數名稱來代表 setter 的傳入參數。
var stringRepresentation: String
get() = this.toString()
set(value) {
setDataFromString(value) // parses the string and assigns values to other properties
}
你也可以自定義私有的 setter 或者透過 inject 工具來實作。
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
Backing Fields
backing field 是什麼?
他其實來自於 Kotlin 的一個特性,當有一段程式碼如下所示。
var name: String? = null
set(value) {
name = value
}
get() = name
看起來合情合理,但是實際上你如果執行看看會發現掉入無窮迴圈,這是為什麼呢?原因就出在 Kotlin 只要去存取 property 的時候,都會先呼叫 get(),這樣一來當我們在 set 這邊要取得 name 這個 property,就會去呼叫 get(),這樣一來就會變成下面這樣。
get() = get() = get() ...
無限跑下去的情況,程式就會掛掉,所以這時候我們就需要用到 backing field,將程式改成以下這樣。
var name: String? = null
set(value) {
field = value
}
get() = field
這時候就可以正常運作,這樣就是 backing field 存在的理由。
Backing Properties
如果你有一個屬性想要封裝起來,你可以宣告另外一個變數來指向原本的私有變數成員,這樣一來,只能透過 getter 的方式取得這個屬性值。
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")
}
Compile-Time Constants
如果你要宣告常數項,在 Java 內通常會宣告成 final static ,如下方所示。
final static String TAG = "TAG_CONST";
在 Kotlin 內可以加入 const 關鍵字,稱作 compile time constants,有以下幾個原則。
- 在最頂層宣告,可以宣告成 object declaration (singleton) 或者寫在 a companion object。
- 必須是原始形態或者字串。
- 不能有 getter。
companion object Factory {
const val TAG = "tag"
}
或者宣告在 object 宣告的 singleton 內部。
object Factory {
const val c = 1
}
Late-Initialized Properties and Variables
你可以透過 lateinit 關鍵字來讓變數晚一點設定值。
class Person() {
private lateinit var name: String
fun setMyName(myName: String) {
name = myName
}
}
Checking whether a lateinit var is initialized (since 1.2)
你可以透過 isInitialized 來判斷該變數是否被初始化。