Kotlin实战指南|Kotlin密封类sealed

转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/120496247
本文出自【赵彦军的博客】

文章目录
  • 简介
  • 密封类与枚举类对比
  • 创建状态集
  • 使用
  • 进一步简化
  • 举例

简介 密封类用来表示受限的类继承结构:当一个值为有限几种的类型, 而不能有任何其他类型时。
在某种意义上,他们是枚举类的扩展:枚举类型的值集合 也是受限的,但每个枚举常量只存在一个实例,而密封类的一个子类可以有可包含状态的多个实例。
声明一个密封类,使用 sealed 修饰类,密封类可以有子类。
sealed 不能修饰 interface ,`abstract classe(会报 warning,但是不会出现编译错误)
密封类与枚举类对比 ① 相同点 ( 类型限制 ) : 从类型种类角度对比 , 类与枚举类类似 , 枚举类的值的集合是受限制的 , 不能随意扩展 ;
② 不同点 ( 对象个数限制 ) : 从每个类型对象个数对比 , 枚举类的每个类型只能存在一个实例 , 而密封类的每个类型可以创建无数个实例 ;
创建状态集 密封类声明:在 class 前添加 sealed 修饰符 , 即可将该类声明为密封类 ;
和抽象类类似,Sealed Class可用于表示层级关系。它的子类可以是任意的类:data class、普通Kotlin对象、普通的类,甚至也可以是另一个密封类,所以,我们定义一个Result Sealed Class:
*/ sealed class Result { //定义网络请求成功 data class OK(val result: String) : Result()//定义网络请求失败 data class FAIL(val throwable: Throwable) : Result() }

当然,也不一定非要写在顶层类中:
//定义结果密封类 sealed class Result//定义网络请求成功 data class OK(val result: String) : Result()//定义网络请求失败 data class FAIL(val throwable: Throwable) : Result()

这样也是可以的,它们的区别在于引用的时候,是否包含顶层类来引用而已。
大部分场景下,还是建议第一种方式,可以比较清晰的展示调用的层级关系。
使用 接下来,我们来看下如何使用Sealed Class。
fun main() { //模拟封装枚举的产生 val result = if (true) { Result.OK("Success") } else Result.FAIL(Exception("error"))when (result) { is Result.OK -> println(result.result)is Result.FAIL -> println(result.throwable) } }

大部分场景下,Sealed Class都会配合when一起使用,同时,如果when的参数是Sealed Class,在IDE中可以快速补全所有分支,而且不会需要你单独补充else 分支,因为Sealed Class已经是完备的了。
所以when和Sealed Class真是天作之合。
进一步简化 其实我们还可以进一步简化代码的调用,因为我们每次使用Sealed Class的时候,都需要when一下,有些时候,也会产生一些代码冗余,所以,借助拓展函数,我们进一步对代码进行简化。
inline fun Result.doSuccess(success: (String) -> Unit) { if (this is Result.OK) { success(result) } }inline fun Result.doError(error: (Exception) -> Unit) { if (this is Result.FAIL) { error(throwable) } }

我们在使用一下:
fun main() { //模拟封装枚举的产生 val result = if (true) { Result.OK("Success") } else Result.FAIL(Exception("error"))result.doSuccess {}result.doError {} }

【Kotlin实战指南|Kotlin密封类sealed】是不是简单很多
举例 假如在 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 关键字。
最后,我们甚至可以把这一组操作封装成一个函数,以便日后调用,如下:
// 先封装一个UI操作列表 class Ui(val uiOps: List = emptyList()) { 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) // 最终调用

    推荐阅读