大家好,kotlin密封类sealed class
不知道大家日常开发中有没有用到,这个在封装网络状态、UI状态等方面都是非常有帮助的;此外kotlin1.5版本还提供了密封接口sealed interface
进一步拓展了密封的使用场景;而且为了方便大家使用,kotlin高版本密封类的受限制层次结构也发生了变化。
如果上面的特性你都不是很了解,没有关系,那就赶紧往下仔细阅读文章了解一下吧。
一. 密封类sealed class
密封类大家可以理解为一个抽象类(其实它本质上就是一个抽象类),定义一些通用的行为或者标识,然后子类继承该密封类,大家看下下面这个例子:
sealed class NetworkState<out T> {
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
分别标识请求成功、失败和断网的具体状态。
由于每次网络请求成功的响应数据、失败异常都有可能不同的,所以这里我们支持Success
和Fail
动态创建,而断网状态是不会发生变化,所以使用了单例创建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出来呢,这里说下本人的看法:
- 定制优化
对密封类进行的when
遍历处理,kotlin1.5.30版本新增了一个特性:
when表达式会对是否罗列出密封类的所有子类并进行处理进行校验,一旦发现漏处理了其中的一个或者多个,会进行告警,高版本1.7及以上会直接给你报错,这对于保证代码安全行方面还是非常的友好的。
如果想要启动这个特性,需要在build中新增如下配置:
kotlin {
sourceSets.all {
languageSettings.apply {
languageVersion = "1.6"
}
}
}
如果kotlin1.7版本以下想要将告警转化为error报错,直接在上面的配置中增加配置progressiveMode = true
即可。
- 方便管理
这个放在下面的密封受限制层次结构的变化中进行讲解。
上面的这种密封类有一个不好的地方就是单继承的问题,本质上密封类的本质就是个抽象类(下面会原理分析),所以子类最多只能继承一个类,而下面的密封接口密封接口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高版本下了这些“毒手”!
七. 参考链接
本文正在参加「金石计划」