kotlin密封sealed class/interface的迭代之旅

語言: CN / TW / HK

highlight: vs theme: devui-blue


大家好,kotlin密封類sealed class不知道大家日常開發中有沒有用到,這個在封裝網絡狀態、UI狀態等方面都是非常有幫助的;此外kotlin1.5版本還提供了密封接口sealed interface進一步拓展了密封的使用場景;而且為了方便大家使用,kotlin高版本密封類的受限制層次結構也發生了變化。

如果上面的特性你都不是很瞭解,沒有關係,那就趕緊往下仔細閲讀文章瞭解一下吧。

一. 密封類sealed class

密封類大家可以理解為一個抽象類(其實它本質上就是一個抽象類),定義一些通用的行為或者標識,然後子類繼承該密封類,大家看下下面這個例子:

``` sealed class NetworkState {

data class Success<T>(val response: T) : NetworkState<T>()

data class Fail(val error: Throwable?) : NetworkState<Nothing>()

object NetDown : NetworkState<Nothing>()

} ```

PS:題外話:為什麼NetworkState類泛型需要增加out聲明?

這裏我們看下Fail類,可以看到它繼承的父類為NetworkState<Nothing>,在kotlin中,Nothing是任意類的子類。這裏的場景簡單表述為就是Nothing可以是A類的子類型,但是卻不代表NetworkState<Nothing>NetworkState<A>的子類型

想要實現這個效果,那就得要讓NetworkState泛型支持協變,在java中使用extends,在kotlin中就得使用out聲明。

這裏我們定義一個密封類NetworkState封裝網絡狀態,以及使用子類Success、Fail、NetDown分別標識請求成功、失敗和斷網的具體狀態。

由於每次網絡請求成功的響應數據、失敗異常都有可能不同的,所以這裏我們支持SuccessFail動態創建,而斷網狀態是不會發生變化,所以使用了單例創建NetDown

在代碼中我們就可以這樣使用:

fun <T> response(state: NetworkState<T>) { when (state) { is NetworkState.Success -> { //響應成功 val data = state.response } is NetworkState.Fail -> { //響應失敗 val error = state.error } is NetworkState.NetDown -> { //斷網處理 } } }

使用起來就如上面所説的,看起來抽象類也能實現這種效果,那為啥kotlin官方要專門搞一個密封類sealed class出來呢,這裏説下本人的看法:

  1. 定製優化

對密封類進行的when遍歷處理,kotlin1.5.30版本新增了一個特性:

when表達式會對是否羅列出密封類的所有子類並進行處理進行校驗,一旦發現漏處理了其中的一個或者多個,會進行告警,高版本1.7及以上會直接給你報錯,這對於保證代碼安全行方面還是非常的友好的。

如果想要啟動這個特性,需要在build中新增如下配置:

kotlin { sourceSets.all { languageSettings.apply { languageVersion = "1.6" } } }

如果kotlin1.7版本以下想要將告警轉化為error報錯,直接在上面的配置中增加配置progressiveMode = true即可。

  1. 方便管理

這個放在下面的密封受限制層次結構的變化中進行講解。

上面的這種密封類有一個不好的地方就是單繼承的問題,本質上密封類的本質就是個抽象類(下面會原理分析),所以子類最多隻能繼承一個類,而下面的密封接口密封接口sealed interface就解決了這個問題:支持子類實現多個密封接口

二. 密封接口sealed interface

密封接口的工作效果和密封類相同,最終的區別就是我們上面提及的一個類可以實現多個密封接口, 接下來我們實際操作下

``` sealed interface Language { val language: String }

sealed interface Location { val location: String }

sealed interface Family { val family: String }

data class Woman( override val language: String, override val location: String, override val family: String ) : Language, Location, Family

data class Child( override val language: String, override val location: String, override val family: String ) : Language, Location, Family ```

使用起來也和上面一樣:

三. 密封受限制繼承層次結構的變化

在kotlin1.5以下,定義的密封類的子類一定要和密封類在同一個文件中,否則就會報錯,比如:

kotlin1.4.30版本中,在kotlin1.5這個文件中新增新增一個密封類

在另一個文件中定義子類繼承該密封類就會報錯:

而到了kotlin1.5.0版本,由於新增了這麼一個特性:

將子類繼承密封類的限制放大到了整個package下,所以同一個包下的其他文件中,定義子類繼承密封類將不會產生報錯

請注意,密封類的子類不能是匿名類和局部類:

四. 密封原理探析

這裏我們簡單瞭解下密封類的實現原理,反編譯看下:

如前面所説,密封類本質上就是個抽象類,下面的子類都是繼承該抽象類:

五. 總結

本篇文章主要是講解了密封類和密封接口的使用及原理,同時對於密封子類的受限制繼承層次結構的變化做了一個説明,還擴展了一個小技巧:如何開啟when遍歷密封類是否羅列所有子類的處理場景並作告警處理的特性以及如何將告警轉化為error報錯。

希望本篇文章能對你所有幫助,可以點贊收藏支持下。

六. 歷史文章

@JvmDefaultWithCompatibility優化小技巧,瞭解一下~

優化@BuilderInference註解,Kotlin高版本下了這些“毒手”!

七. 參考鏈接

https://kotlinlang.org/docs/whatsnew15.html


本文正在參加「金石計劃」