數(shù)據(jù)類
我們經(jīng)常創(chuàng)建一些只保存數(shù)據(jù)的類。在這些類中逗栽,一些標(biāo)準(zhǔn)函數(shù)往往是從數(shù)據(jù)機(jī)械推導(dǎo)而來的誊爹。
Kotlin 中使用關(guān)鍵字 data 來創(chuàng)建一個(gè)只包含數(shù)據(jù)的類
data class User(val name: String, val age: Int)
編譯器會自動(dòng)的從主構(gòu)造函數(shù)中根據(jù)所有聲明的屬性提取以下函數(shù):
- equals()/hashCode() 對
- toString() 格式如 "User(name=John, age=42)"
- componentN() 函數(shù)按聲明順序芬首,對應(yīng)所有屬性
- copy() 函數(shù)
Note: compoentN() 函數(shù)是在 kotlin 中廣泛使用的約定原則的另一個(gè)樣例,需要用 operator 關(guān)鍵字標(biāo)記吮成,以允許在解構(gòu)聲明中使用它們橱乱。更多見后續(xù)的解構(gòu)聲明
如果這些函數(shù)在類中已經(jīng)被明確定義了,或者從超類中繼承而來赁豆,就不再會生成仅醇。
為了保證生成代碼的一致性以及有意義,數(shù)據(jù)類需要滿足以下條件:
- 主構(gòu)造函數(shù)至少包含一個(gè)參數(shù)
- 所有的主構(gòu)造函數(shù)的參數(shù)必須標(biāo)識為 val 或者 var
- 數(shù)據(jù)類不可以聲明為 abstract, open, sealed 或者 inner
- (在1.1之前)數(shù)據(jù)類只能實(shí)現(xiàn)接口魔种。 從 1.1 起析二,數(shù)據(jù)類可以擴(kuò)展其他類。
此外成員生成遵循關(guān)于成員繼承的這些規(guī)則
- 如果在數(shù)據(jù)類體中有顯式實(shí)現(xiàn)equals()、hashCode()或者toString()叶摄,或者這些函數(shù)在父類中有 final 實(shí)現(xiàn)属韧,那么不會生成這些函數(shù),而會使用現(xiàn)有函數(shù)蛤吓;
- 如果超類型具有 open 的 componentN() 函數(shù)并且返回兼容的類型宵喂,那么會為數(shù)據(jù)類生成相應(yīng)的函數(shù),并覆蓋超類的實(shí)現(xiàn)会傲。如果超類型的這些函數(shù)由于簽名不兼容或者是 final 而導(dǎo)致無法覆蓋锅棕,那么會報(bào)錯(cuò);
- 不允許為 componentN() 以及 copy() 函數(shù)提供顯式實(shí)現(xiàn)淌山。
在 JVM 中裸燎,如果生成的類需要含有一個(gè)無參的構(gòu)造函數(shù),則所有的屬性必須指定默認(rèn)值
data class User(val name: String = "", val age: Int = 0)
復(fù)制
在很多情況下泼疑,我們需要復(fù)制一個(gè)對象改變它的某些屬性德绿,但其余部分保持不變。copy() 函數(shù)就是為此而生成退渗。
復(fù)制使用 copy() 函數(shù),對于上文的 User 類移稳,其實(shí)現(xiàn)會類似下面這樣:
fun copy(name: String = this.name, age: Int = this.age) = User(name, age)
下面是使用
fun main(args: Array<String>) {
val jack = User(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)
println(jack)
println(olderJack)
}
輸出結(jié)果為:
User(name=Jack, age=1)
User(name=Jack, age=2)
數(shù)據(jù)類以及解構(gòu)聲明
為數(shù)據(jù)類生成的 Component 函數(shù) 使它們可在解構(gòu)聲明中使用:
val jane = User("Jane", 35)
val (name, age) = jane
println("$name, $age years of age") // prints "Jane, 35 years of age"
標(biāo)準(zhǔn)數(shù)據(jù)類
標(biāo)準(zhǔn)庫提供了 Pair 和 Triple。盡管在大多數(shù)情形中会油,命名數(shù)據(jù)類是更好的設(shè)計(jì)選擇个粱,因?yàn)檫@樣代碼可讀性更強(qiáng)而且提供了有意義的名字和屬性。
- Pair 用于存儲一對值(或鍵值)
- Triple 用于存儲三值
密封類
密封類是指類中只有這幾種類型的值钞啸,而不能有其它的類型值几蜻。它跟我們通用的枚舉或Android中的魔法常量很像(@IntDef)。區(qū)別是枚舉常量只有一個(gè)實(shí)例体斩,而密封類只是值受限,但類的實(shí)例可以有多個(gè)颖低。
官方的解釋為:
Sealed classes are used for representing restricted class hierarchies, when a value can have one of the types from a limited set, but cannot have any other type. They are, in a sense, an extension of enum classes: the set of values for an enum type is also restricted, but each enum constant exists only as a single instance, whereas a subclass of a sealed class can have multiple instances which can contain state.
即:
密封類用來表示受限的類繼承結(jié)構(gòu):當(dāng)一個(gè)值為有限集中的類型, 而不能有任何其他類型時(shí)絮吵。
Kotlin中使用關(guān)鍵字 sealed 來聲明一個(gè)密封類,但需要注意:
- 密封類可以有子類忱屑,但是所有子類都必須在與密封類自身相同的文件中聲明蹬敲,
在Kotlin 1.1 之前,該規(guī)則更加嚴(yán)格:子類必須嵌套在密封類聲明的內(nèi)部 - sealed 不能修飾 interface ,abstract class(會報(bào) warning,但是不會出現(xiàn)編譯錯(cuò)誤)
- 密封類不允許有非 private 構(gòu)造函數(shù)(其構(gòu)造函數(shù)默認(rèn)為 private)
- 擴(kuò)展密封類子類的類(間接繼承者)可以放在任何位置莺戒,而無需在同一個(gè)文件中
sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
fun eval(expr: Expr): Double = when (expr) {
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
NotANumber -> Double.NaN
}
使用密封類的關(guān)鍵好處在于使用 when 表達(dá)式 的時(shí)候伴嗡,如果能夠 驗(yàn)證語句覆蓋了所有情況,就不需要為該語句再添加一個(gè) else 子句了从铲。
fun eval(expr: Expr): Double = when(expr) {
is Expr.Const -> expr.number
is Expr.Sum -> eval(expr.e1) + eval(expr.e2)
Expr.NotANumber -> Double.NaN
// 不再需要 `else` 子句瘪校,因?yàn)槲覀円呀?jīng)覆蓋了所有的情況
}