最近在看DataStore源码时,看到一种类似于枚举的使用方式。就是Sealed类,但是比枚举类更有扩展性,除了拥有共享属性,还可以持有特征属性。
1、通常限定一些有限集合的状态值.
如: 网络状态:成功,失败.
账户状态:vip,普通.
2、与when配合使用,无需if-else
先讲两个使用案例:
第一个案例:
/**
* Represents the current state of the DataStore.
*/
private sealed class State<T>
private object UnInitialized : State<Any>()
/**
* A read from disk has succeeded, value represents the current on disk state.
*/
private class Data<T>(val value: T, val hashCode: Int) : State<T>() {
fun checkHashCode() {
check(value.hashCode() == hashCode) {
"Data in DataStore was mutated but DataStore is only compatible with Immutable types."
}
}
}
/**
* A read from disk has failed. ReadException is the exception that was thrown.
*/
private class ReadException<T>(val readException: Throwable) : State<T>()
/**
* The scope has been cancelled. This DataStore cannot process any new reads or writes.
*/
private class Final<T>(val finalException: Throwable) : State<T>()
上述代码中,UnInitialized,Data,ReadException,Final都是State的四种不同状态。相当于,枚举类State,有这四个状态成员。
State状态类,虽然没有共享属性,但是可以有;
UnInitialized状态类,虽然没有特征属性,但是也可以有;
Data状态类,有特征属性:value, hashcode;
ReadException状态类,有特征属性:readException;
Final状态类,有特征属性finalException。
当然也可以有属于自己的方法,Data状态类有就:fun checkHashCode().
使用时可以这样使用:
密封类通常与表达式一起使用。
在判断出状态类型后,根据自带的特有属性顺便在状态里把事情做了。
when (it) {
is ReadException<T> -> throw it.readException
is Final<T> -> throw it.finalException
is Data<T> -> it.value
is UnInitialized -> error(
"This is a bug in DataStore. Please file a bug at: " +
"https://issuetracker.google.com/issues/new?" +
"component=907884&template=1466542"
)
}
有没有发现?不需要else处理default,因为编译器知道Sealed类的所有子状态类,就没必要了。这是区别于普通类的情况。
第二个案例:
这个案例是想说, 通过扩展函数还可以做进一步的判断,执行更多的业务。
sealed class Result<out R> {
data class Success<out T>(val data: T) : Result<T>()
data class Error(val throwable: Throwable?) : Result<Nothing>()
object Loading : Result<Nothing>()
override fun toString(): String {
return when (this) {
is Success<*> -> "Success[data=$data]"
is Error -> "Error[throwable=$throwable]"
Loading -> "Loading"
}
}
}
val Result<*>.succeeded
get() = this is Success && data != null
fun <T> Result<T>.successOr(fallback: T): T {
return (this as? Success<T>)?.data ?: fallback
}
val <T> Result<T>.data: T?
get() = (this as? Success)?.data
inline fun <reified T> Result<T>.updateOnSuccess(stateFlow: MutableStateFlow<T>) {
if (this is Success) {
stateFlow.value = data
}
}
总结一下Sealed类:
- 密封类以及它的子类最好都定义在同一个文件中,这样会更加内聚。但不是必须的! 最新的kotlin版本只要求同Module、同包名
- 密封类隐式是一个无法实例化的抽象类。
- 它用于表示受限制的类层次结构。
- 当对象具有来自有限集的类型之一,但不能具有任何其他类型时。这是使用密封类。类似于枚举使用的地方。
- 密封类的构造函数在默认情况下是私有的,它也不能允许声明为非私有。
- 对比普通类,Sealed类自动生成toString、hashCode、equals函数;
- 结合扩展函数做更进一步的事情。
从1.5开始,有增加了Sealed接口。没有任何成员定义的Sealed类,可以用Sealed接口替代。