前言
上一篇,我們學習了Kotlin中的拓展,今天繼續來學習Kotlin中的數據類和密封類。
數據類
我們經常創建一些只保存數據的類。 在這些類中,一些標準函數往往是從數據機械推導而來的。在 Kotlin 中,這叫做 數據類 並標記爲 data:
data class User(val name: String, val age: Int)
爲什麼要引入數據類?我們先看一個Java POJO(普通Java對象)
class Dog{
private String name;
private String sound;
private String age;
public String getName() {
return name;
}
public void setName(String name) {
this .name = name;
}
public String getSound() {
return sound;
}
public void setSound(String sound) {
this .sound = sound;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this .age = age;
}
所有這些儀式都用於一個簡單的Java POJO類,該類用於保存一些數據。 在對來自API的JSON響應進行序列化/反序列化時,您可能使用過此類。 它爲您提供屬性的獲取器和設置器。
現在,讓我們看看如何藉助數據類在Kotlin中完成此操作:
data class Dog(var name: String, var age: String, var sound: String)
沒錯,只需一行! 我們可以像使用Java類一樣繼續使用此類。 獲取器和設置器是爲我們生成的編譯器。編譯器會自動的從主構造函數中根據所有聲明的屬性提取以下函數:
- equals() / hashCode()
- toString() 格式如 “Dog(name=John, age=42,sound=喵喵)”
- componentN() functions 對應於屬性,按聲明順序排列
- copy() 函數
如果這些函數在類中已經被明確定義了,或者從超類中繼承而來,就不再會生成。
爲了保證生成代碼的一致性以及有意義,數據類需要滿足以下條件:
- 主構造函數至少包含一個參數。
- 所有的主構造函數的參數必須標識爲val 或者 var ;
- 據類不可以聲明爲 abstract, open, sealed 或者 inner;
- 數據類不能繼承其他類 (但是可以實現接口)。
在類體中聲明的屬性
請注意,對於那些自動生成的函數,編譯器只使用在主構造函數內部定義的屬性。如需在生成的實現中排除一個屬性,請將其聲明在類體中:
data class Person(val name: String) {
var age: Int = 0
}
在 toString()、 equals()、 hashCode() 以及 copy() 的實現中只會用到 name 屬性,並且只有一個 component 函數 component1(),且這個component1返回的是name的值。
複製
複製使用 copy() 函數,我們可以使用該函數複製對象並修改部分屬性, 其實現會類似下面這樣:
data class Cat(var name: String, var age: String, var sound: String)
var cat = Cat("Jim","2","喵喵")
var catTom = cat.copy(name="Tom")
println("catTom'name is ${catTom.name}")
//打印結果:catTom'name is Tom
componentN
使用數據類對象調用componentN函數可以按聲明順序獲取數據類中的屬性值,但不包括再類體中聲明的屬性。
data class Cat(var name: String, var age: String, var sound: String){
//類體中聲明屬性
var height:Int = 30
}
var cat = Cat("Jim","2","喵喵")
var catTom = cat.copy(name="Tom")
println("catTom'name is ${catTom.name} component1 : ${cat.component1()} component2 : ${cat.component2()} component3 : ${cat.component3()}")
//打印結果:catTom'name is Tom component1 : Jim component2 : 2 component3 : 喵喵
解構聲明
組件函數允許數據類在解構聲明中使用:
var cat = Cat("Jim","2","喵喵")
val (name,age,sound) = cat
println("$name $age $sound")
//打印結果:Jim 2 喵喵
有時把一個對象 解構 成很多變量會很方便:
val (name,age,sound) = cat
這種語法稱爲 解構聲明 。一個解構聲明同時創建多個變量。 我們已經聲明瞭兩個新變量: name 和 age,sound並且可以獨立使用它們:
...
println("$name $age $sound")
...
以上等價於:
...
println("${cat.component1()} ${cat.component2()} ${cat.component3()}")
...
密封類
密封類用來表示受限的類繼承結構:當一個值爲有限幾種的類型, 而不能有任何其他類型時。在某種意義上,他們是枚舉類的擴展:枚舉類型的值集合 也是受限的,但每個枚舉常量只存在一個實例,而密封類 的一個子類可以有可包含狀態的多個實例。
聲明一個密封類,使用 sealed 修飾類,密封類可以有子類,但是所有的子類都必須要內嵌在密封類中。sealed 不能修飾 interface ,abstract class(會報 warning,但是不會出現編譯錯誤)
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 Expr.Const -> expr.number
is Expr.Sum -> eval(expr.e1) + eval(expr.e2)
Expr.NotANumber -> Double.NaN
// 不再需要 `else` 子句,因爲我們已經覆蓋了所有的情況
}
密封類就是一種專門用來配合 when 語句使用的類。
舉個例子,假如在 Android 中我們有一個 view,我們現在想通過 when 語句設置針對 view 進行兩種操作:顯示和隱藏,那麼就可以這樣做:
sealed class UiOp {
object Show: UiOp()
object Hide: UiOp()
}
fun execute(view: View, op: UiOp) = when (op) {
UiOp.Show -> view.visibility = View.VISIBLE
UiOp.Hide -> view.visibility = View.GONE
}
以上功能其實完全可以用枚舉實現,但是如果我們現在想加兩個操作:水平平移和縱向平移,並且還要攜帶一些數據,比如平移了多少距離,平移過程的動畫類型等數據,用枚舉顯然就不太好辦了,這時密封類的優勢就可以發揮了,例如:
sealed class UiOp {
object Show: UiOp()
object Hide: UiOp()
class TranslateX(val px: Float): UiOp()
class TranslateY(val px: Float): UiOp()
}
fun execute(view: View, op: UiOp) = when (op) {
UiOp.Show -> view.visibility = View.VISIBLE
UiOp.Hide -> view.visibility = View.GONE
is UiOp.TranslateX -> view.translationX = op.px // 這個 when 語句分支不僅告訴 view 要水平移動,還告訴 view 需要移動多少距離,這是枚舉等 Java 傳統思想不容易實現的
is UiOp.TranslateY -> view.translationY = op.px
}
以上代碼中,TranslateX 是一個類,它可以攜帶多於一個的信息,比如除了告訴 view 需要水平平移之外,還可以告訴 view 平移多少像素,甚至還可以告訴 view 平移的動畫類型等信息,我想這大概就是密封類出現的意義吧。
除此之外,如果 when 語句的分支不需要攜帶除“顯示或隱藏view之外的其它信息”時(即只需要表明 when 語句分支,不需要攜帶額外數據時),用 object 關鍵字創建單例就可以了,並且此時 when 子句不需要使用 is 關鍵字。只有需要攜帶額外信息時才定義密封類的子類,而且使用了密封類就不需要使用 else 子句,每當我們多增加一個密封類的子類或單例,編譯器就會在 when 語句中給出提示,可以在編譯階段就及時發現錯誤,這也是以往 switch-case 語句和枚舉不具備的功能。
最後,我們甚至可以把這一組操作封裝成一個函數,以便日後調用,如下:
// 先封裝一個UI操作列表
class Ui(val uiOps: List = emptyList()) {
//這裏是用operator關鍵字對+進行重載
operator fun plus(uiOp: UiOp) = Ui(uiOps + uiOp)
}
// 定義一組操作
val ui = Ui() +
UiOp.Show +
UiOp.TranslateX(20f) +
UiOp.TranslateY(40f) +
UiOp.Hide
// 定義調用的函數
fun run(view: View, ui: Ui) {
ui.uiOps.forEach { execute(view, it) }
}
run(view, ui) // 最終調用
尾巴
今天的學習筆記就先到這裏了,下一篇我們將學習Kotlin中的泛型。
老規矩,喜歡我的文章,歡迎素質三連:點贊,評論,關注,謝謝大家!